1"""
2PySCeS - Python Simulator for Cellular Systems (http://pysces.sourceforge.net)
3
4Copyright (C) 2004-2022 B.G. Olivier, J.M. Rohwer, J.-H.S Hofmeyr all rights reserved,
5
6Brett G. Olivier (bgoli@users.sourceforge.net)
7Triple-J Group for Molecular Cell Physiology
8Stellenbosch University, South Africa.
9
10Permission to use, modify, and distribute this software is given under the
11terms of the PySceS (BSD style) license. See LICENSE.txt that came with
12this distribution for specifics.
13
14NO WARRANTY IS EXPRESSED OR IMPLIED.  USE AT YOUR OWN RISK.
15Brett G. Olivier
16"""
17from __future__ import division, print_function
18from __future__ import absolute_import
19from __future__ import unicode_literals
20
21"""
22TODO: Parameter elasticities wrt the compartments
23"""
24
25from pysces.version import __version__
26
27__doc__ = '''
28            PyscesModel
29            -----------
30
31            This module contains the core PySCeS classes which
32            create the model and associated data objects
33
34            '''
35import os, copy, gc, time
36import math, operator, re
37import pprint, pickle, io
38import warnings
39
40try:
41    input = raw_input  # Py2 compatibility
42except NameError:
43    pass
44import numpy
45import scipy
46import scipy.linalg
47import scipy.integrate
48
49ScipyDerivative = None
50HAVE_SCIPY_DERIV = False
51try:
52    ScipyDerivative = scipy.derivative
53    HAVE_SCIPY_DERIV = True
54except AttributeError:
55    try:
56        import scipy.misc
57
58        ScipyDerivative = scipy.misc.derivative
59        HAVE_SCIPY_DERIV = True
60    except ImportError as AttributeError:
61        pass
62if not HAVE_SCIPY_DERIV:
63    raise RuntimeError('\nSciPy derivative function not available')
64
65from getpass import getuser
66
67from . import model_dir as MODEL_DIR
68from . import output_dir as OUTPUT_DIR
69from . import install_dir as INSTALL_DIR
70from . import PyscesRandom as random
71from .PyscesScan import Scanner
72from .core2.InfixParser import MyInfixParser
73
74from . import (
75    nleq2,
76    nleq2_switch,
77    pitcon,
78    plt,
79    gplt,
80    PyscesStoich,
81    PyscesParse,
82    __SILENT_START__,
83    __CHGDIR_ON_START__,
84    SED,
85)
86
87if __CHGDIR_ON_START__:
88    CWD = OUTPUT_DIR
89else:
90    CWD = os.getcwd()
91
92interface = None
93
94# this is incredibly crude but effectively masks off unsupported random functions
95del (
96    random.setstate,
97    random.getstate,
98)  # random.division,
99del random.randrange, random.Random, random.choice
100del random.sample, random.shuffle, random.jumpahead
101del random.SystemRandom, random.WichmannHill, random.triangular
102# used by functions random.NV_MAGICCONST, random.SG_MAGICCONST, random.BPF, random.RECIP_BPF
103
104
105# Scipy version check
106if (
107    int(scipy.__version__.split('.')[0]) < 1
108    and int(scipy.__version__.split('.')[1]) < 6
109):
110    print(
111        '\nINFO: Your version of SciPy ('
112        + scipy.version.version
113        + ') might be too old\n\tVersion 0.6.x or newer is strongly recommended\n'
114    )
115else:
116    if not __SILENT_START__:
117        print(
118            'You are using NumPy ({}) with SciPy ({})'.format(
119                numpy.__version__, scipy.__version__
120            )
121        )
122
123_HAVE_ASSIMULO = False
124_ASSIMULO_LOAD_ERROR = ''
125
126try:
127    with warnings.catch_warnings():
128        warnings.filterwarnings("ignore", category=numpy.VisibleDeprecationWarning)
129        from assimulo.solvers import CVode
130        from assimulo.problem import Explicit_Problem
131    _HAVE_ASSIMULO = True
132    if not __SILENT_START__:
133        print('Assimulo CVode available')
134
135except Exception as ex:
136    _ASSIMULO_LOAD_ERROR = '{}'.format(ex)
137    _HAVE_ASSIMULO = False
138
139if _HAVE_ASSIMULO:
140    class EventsProblem(Explicit_Problem):
141        def __init__(self, mod, **kwargs):
142            Explicit_Problem.__init__(self, **kwargs)
143            self.mod = mod
144            self.name = self.mod.__KeyWords__['Modelname']
145            # needed to track changes in parameters during events for REq evaluation
146            self.parvals = []
147            self.parvals.append([getattr(self.mod, p) for p in self.mod.parameters])
148            self.event_times = []
149
150        def state_events(self, t, y, sw):
151            self.events = self.mod.__events__
152            eout = numpy.zeros(len(self.events))
153            self.mod._TIME_ = t
154            for ev in range(len(self.events)):
155                if self.events[ev](t):
156                    eout[ev] = 0
157                else:
158                    eout[ev] = 1
159            if self.mod.__HAS_RATE_RULES__:
160                exec(self.mod.__CODE_raterule)
161            return eout
162
163        def handle_event(self, solver, event_info):
164            self.event_times.append(solver.t)
165            state_info = event_info[0]
166            idx = state_info.index(-1)
167            ev = self.events[idx]
168            if ev._assign_now:
169                for ass in ev.assignments:
170                    if ass.variable in self.mod.L0matrix.getLabels()[1] or (
171                        self.mod.mode_integrate_all_odes
172                        and ass.variable in self.mod.__species__
173                    ):
174                        assVal = ass.getValue()
175                        assIdx = self.mod.__species__.index(ass.variable)
176                        if self.mod.__KeyWords__["Species_In_Conc"]:
177                            solver.y[assIdx] = assVal * getattr(
178                                self.mod, self.mod.__CsizeAllIdx__[assIdx]
179                            )
180                        else:
181                            solver.y[assIdx] = assVal
182                    elif (
183                        not self.mod.mode_integrate_all_odes
184                        and ass.variable in self.mod.L0matrix.getLabels()[0]
185                    ):
186                        print(
187                            'Event assignment to dependent species consider setting "mod.mode_integrate_all_odes = True"'
188                        )
189                    elif (
190                        self.mod.__HAS_RATE_RULES__ and ass.variable in self.mod.__rate_rules__
191                    ):
192                        assVal = ass.getValue()
193                        rrIdx = self.mod.__rate_rules__.index(ass.variable)
194                        self.mod.__rrule__[rrIdx] = assVal
195                        solver.y[self.mod.L0matrix.shape[1] + rrIdx] = assVal
196                        setattr(self.mod, ass.variable, assVal)
197                    else:
198                        ass()
199            # track any parameter changes
200            self.parvals.append([getattr(self.mod, p) for p in self.mod.parameters])
201
202
203# for future fun
204_HAVE_VPYTHON = False
205_VPYTHON_LOAD_ERROR = ''
206__psyco_active__ = 0
207'''
208try:
209    import visual
210    _HAVE_VPYTHON = True
211    vpyscene = visual.display(x=150, y=50, title='PySCeS '+__version__+' - C Brett G. Olivier, Stellenbosch 2022', width=640, height=480,\
212    center=(0,0,0), background=(0,0,0))
213    vpyscene.select()
214    # vpyscene.autocenter = True
215    l1 = visual.label(pos=(0,0,0), text="Welcome to the PySCeS Visualisation Terminal", color=visual.color.red, box=0)
216    l2 = visual.label(pos=(0,0.5,0.5), text="It just works!", color=visual.color.green, box=0)
217except Exception, ex:
218    _VPYTHON_LOAD_ERROR = '%s' % ex
219    _HAVE_VPYTHON = False
220try:
221    import psyco
222    psyco.profile()
223    __psyco_active__ = 1
224    print('PySCeS Model module is now PsycoActive!')
225except:
226    __psyco_active__ = 0
227'''
228# machine specs
229mach_spec = numpy.MachAr()
230# grab a parser
231pscParser = PyscesParse.PySCeSParser(debug=0)
232
233
234def chkpsc(File):
235    """
236    chkpsc(File)
237
238    Chekc whether the filename "File" has a '.psc' extension and adds one if not.
239
240    Arguments:
241
242    File: filename string
243
244    """
245    try:
246        if File[-4:] == '.psc':
247            pass
248        else:
249            print('Assuming extension is .psc')
250            File += '.psc'
251    except:
252        print('Chkpsc error')
253    return File
254
255
256def chkmdir():
257    """
258    chkmdir()
259
260    Import and grab pysces.model_dir
261
262    Arguments:
263    None
264
265    """
266    ##  from pysces import model_dir as MODEL_DIR
267    pass
268
269
270class BagOfStuff(object):
271    """
272    A collection of attributes defined by row and column lists
273    used by Response coefficients etc matrix is an array of values
274    while row/col are lists of row colummn name strings
275    """
276
277    matrix = None
278    row = None
279    col = None
280
281    def __init__(self, matrix, row, col):
282        "Load attributes from arrays"
283        assert len(row) == matrix.shape[0], "\nRow dimension mismatch"
284        assert len(col) == matrix.shape[1], "\nCol dimension mismatch"
285        self.matrix = matrix
286        self.row = list(row)
287        self.col = list(col)
288
289    def load(self):
290        for r in range(self.matrix.shape[0]):
291            for c in range(self.matrix.shape[1]):
292                setattr(
293                    self, str(self.row[r]) + '_' + str(self.col[c]), self.matrix[r, c]
294                )
295
296    def get(self, attr1, attr2):
297        "Returns a single attribute \"attr1_attr2\" or None"
298        try:
299            return getattr(self, attr1 + '_' + attr2)
300        except Exception as ex:
301            print(ex)
302            return None
303
304    def select(self, attr, search='a'):
305        """Return a dictionary of <attr>_<name>, <name>_<attr> : val or {} if none
306        If attr exists as an index for both left and right attr then:
307        search='a' : both left and right attributes (default)
308        search='l' : left attributes only
309        search='r' : right attributes"""
310        output = {}
311
312        if attr in self.row and attr in self.col:
313            if search in ['a', 'l']:
314                row = self.row.index(attr)
315                for col in range(self.matrix.shape[1]):
316                    output.setdefault(attr + '_' + self.col[col], self.matrix[row, col])
317            if search in ['a', 'r']:
318                col = self.col.index(attr)
319                for row in range(self.matrix.shape[0]):
320                    output.setdefault(self.row[row] + '_' + attr, self.matrix[row, col])
321        elif attr in self.row:
322            row = self.row.index(attr)
323            for col in range(self.matrix.shape[1]):
324                output.setdefault(attr + '_' + self.col[col], self.matrix[row, col])
325        elif attr in self.col:
326            col = self.col.index(attr)
327            for row in range(self.matrix.shape[0]):
328                output.setdefault(self.row[row] + '_' + attr, self.matrix[row, col])
329        return output
330
331    def list(self):
332        """
333        Return all attributes as a attr:val dictionary
334
335        """
336        output = {}
337        for row in range(self.matrix.shape[0]):
338            for col in range(self.matrix.shape[1]):
339                output.setdefault(
340                    self.row[row] + '_' + self.col[col], self.matrix[row, col]
341                )
342        return output
343
344    def listAllOrdered(self, order='descending', absolute=True):
345        """
346        Return an ordered list of (attr, value) tuples
347
348         - *order* [default='descending'] the order to return as: 'descending' or 'ascending'
349         - *absolute* [default=True] use the absolute value
350
351        """
352        if order != 'ascending':
353            order = True
354        else:
355            order = False
356
357        output_v = []
358        output_n = []
359        for row in range(self.matrix.shape[0]):
360            for col in range(self.matrix.shape[1]):
361                output_v.append(self.matrix[row, col])
362                output_n.append(self.row[row] + '_' + self.col[col])
363        if absolute:
364            new_idx = numpy.argsort([abs(v) for v in output_v])
365        else:
366            new_idx = numpy.argsort(output_v)
367        output = []
368        if order:
369            new_idx = new_idx.tolist()
370            new_idx.reverse()
371        for i_ in new_idx:
372            output.append((output_n[i_], output_v[i_]))
373        return output
374
375
376class ScanDataObj(object):
377    """
378    New class used to store parameter scan data (uses StateDataObj)
379    """
380
381    species = None
382    fluxes = None
383    rules = None
384    xdata = None
385    mod_data = None
386    flux_labels = None
387    species_labels = None
388    rules_labels = None
389    xdata_labels = None
390    mod_data_labels = None
391    invalid_states = None
392    parameters = None
393    parameter_labels = None
394    HAS_FLUXES = False
395    HAS_SPECIES = False
396    HAS_RULES = False
397    HAS_XDATA = False
398    HAS_MOD_DATA = False
399    HAS_SET_LABELS = False
400    ALL_VALID = True
401    OPEN = True
402
403    def __init__(self, par_label):
404        self.species = []
405        self.fluxes = []
406        self.rules = []
407        self.xdata = []
408        self.mod_data = []
409        self.invalid_states = []
410        self.parameters = []
411        if isinstance(par_label, list):
412            self.parameter_labels = par_label
413        else:
414            self.parameter_labels = [par_label]
415
416    def setLabels(self, ssdata):
417        if ssdata.HAS_SPECIES:
418            self.species_labels = ssdata.species_labels
419            self.HAS_SPECIES = True
420        if ssdata.HAS_FLUXES:
421            self.flux_labels = ssdata.flux_labels
422            self.HAS_FLUXES = True
423        if ssdata.HAS_RULES:
424            self.rules_labels = ssdata.rules_labels
425            self.HAS_RULES = True
426        if ssdata.HAS_XDATA:
427            self.xdata_labels = ssdata.xdata_labels
428            self.HAS_XDATA = True
429        self.HAS_SET_LABELS = True
430
431    def addPoint(self, ipar, ssdata):
432        """takes a list/array of input parameter values and the associated ssdata object"""
433        assert self.OPEN, '\nScan has been finalised no new data may be added'
434        if not self.HAS_SET_LABELS:
435            self.setLabels(ssdata)
436
437        if hasattr(ipar, '__iter__'):
438            self.parameters.append(ipar)
439        else:
440            self.parameters.append([ipar])
441        if self.HAS_SPECIES:
442            self.species.append(ssdata.getSpecies())
443        if self.HAS_FLUXES:
444            self.fluxes.append(ssdata.getFluxes())
445        if self.HAS_RULES:
446            self.rules.append(ssdata.getRules())
447        if self.HAS_XDATA:
448            self.xdata.append(ssdata.getXData())
449        if not ssdata.IS_VALID:
450            self.ALL_VALID = False
451            self.invalid_states.append(ipar)
452
453    def addModData(self, mod, *args):
454        if not self.HAS_MOD_DATA:
455            self.HAS_MOD_DATA = True
456            self.mod_data_labels = [attr for attr in args if hasattr(mod, attr)]
457        self.mod_data.append(
458            tuple([getattr(mod, attr) for attr in self.mod_data_labels])
459        )
460
461    def closeScan(self):
462        print('\nINFO: closing scan no new data may be added.')
463        if self.HAS_SPECIES:
464            self.species = numpy.array(self.species)
465        if self.HAS_FLUXES:
466            self.fluxes = numpy.array(self.fluxes)
467        if self.HAS_RULES:
468            self.rules = numpy.array(self.rules)
469        if self.HAS_XDATA:
470            self.xdata = numpy.array(self.xdata)
471        if self.HAS_MOD_DATA:
472            self.mod_data = numpy.array(self.mod_data)
473        self.parameters = numpy.array(self.parameters)
474        self.OPEN = False
475
476    def getSpecies(self, lbls=False):
477        if self.OPEN:
478            self.closeScan()
479        output = None
480        labels = None
481        if self.HAS_SPECIES:
482            output = numpy.hstack((self.parameters, self.species))
483            labels = self.parameter_labels + self.species_labels
484        if lbls:
485            return output, labels
486        else:
487            return output
488
489    def getFluxes(self, lbls=False):
490        if self.OPEN:
491            self.closeScan()
492        output = None
493        labels = None
494        if self.HAS_FLUXES:
495            output = numpy.hstack((self.parameters, self.fluxes))
496            labels = self.parameter_labels + self.flux_labels
497        if lbls:
498            return output, labels
499        else:
500            return output
501
502    def getRules(self, lbls=False):
503        if self.OPEN:
504            self.closeScan()
505        output = None
506        labels = None
507        if self.HAS_RULES:
508            output = numpy.hstack((self.parameters, self.rules))
509            labels = self.parameter_labels + self.rules_labels
510        if lbls:
511            return output, labels
512        else:
513            return output
514
515    def getXData(self, lbls=False):
516        if self.OPEN:
517            self.closeScan()
518        output = None
519        labels = None
520        if self.HAS_XDATA:
521            output = numpy.hstack((self.parameters, self.xdata))
522            labels = self.parameter_labels + self.xdata_labels
523        if lbls:
524            return output, labels
525        else:
526            return output
527
528    def getModData(self, lbls=False):
529        if self.OPEN:
530            self.closeScan()
531        output = None
532        labels = None
533        if self.HAS_MOD_DATA:
534            output = numpy.hstack((self.parameters, self.mod_data))
535            labels = self.parameter_labels + self.mod_data_labels
536        if lbls:
537            return output, labels
538        else:
539            return output
540
541    def getAllScanData(self, lbls=False):
542        if self.OPEN:
543            self.closeScan()
544        output = self.parameters
545        labels = self.parameter_labels
546        if self.HAS_SPECIES:
547            output = numpy.hstack((output, self.species))
548            labels = labels + self.species_labels
549        if self.HAS_FLUXES:
550            output = numpy.hstack((output, self.fluxes))
551            labels = labels + self.flux_labels
552        if self.HAS_RULES:
553            output = numpy.hstack((output, self.rules))
554            labels = labels + self.rules_labels
555        if self.HAS_XDATA:
556            output = numpy.hstack((output, self.xdata))
557            labels = labels + self.xdata_labels
558        if self.HAS_MOD_DATA:
559            output = numpy.hstack((output, self.mod_data))
560            labels = labels + self.mod_data_labels
561        if lbls:
562            return output, labels
563        else:
564            return output
565
566    def getScanData(self, *args, **kwargs):
567        """
568        getScanData(\*args) feed this method species/flux/rule/mod labels and it
569        will return an array of [parameter(s), sp1, f1, ....]
570        """
571        if 'lbls' in kwargs:
572            lbls = kwargs['lbls']
573        else:
574            lbls = False
575
576        output = self.parameters
577        lout = self.parameter_labels
578        for roc in args:
579            if self.HAS_SPECIES and roc in self.species_labels:
580                lout.append(roc)
581                output = numpy.hstack(
582                    (
583                        output,
584                        self.species.take([self.species_labels.index(roc)], axis=-1),
585                    )
586                )
587            if self.HAS_FLUXES and roc in self.flux_labels:
588                lout.append(roc)
589                output = numpy.hstack(
590                    (output, self.fluxes.take([self.flux_labels.index(roc)], axis=-1))
591                )
592            if self.HAS_RULES and roc in self.rules_labels:
593                lout.append(roc)
594                output = numpy.hstack(
595                    (output, self.rules.take([self.rules_labels.index(roc)], axis=-1))
596                )
597            if self.HAS_XDATA and roc in self.xdata_labels:
598                lout.append(roc)
599                output = numpy.hstack(
600                    (output, self.xdata.take([self.xdata_labels.index(roc)], axis=-1))
601                )
602            if self.HAS_MOD_DATA and roc in self.mod_data_labels:
603                lout.append(roc)
604                output = numpy.hstack(
605                    (
606                        output,
607                        self.mod_data.take([self.mod_data_labels.index(roc)], axis=-1),
608                    )
609                )
610        if not lbls:
611            return output
612        else:
613            return output, lout
614
615
616class StateDataObj(object):
617    """
618    New class used to store steady-state data.
619    """
620
621    fluxes = None
622    species = None
623    rules = None
624    xdata = None
625    flux_labels = None
626    species_labels = None
627    rules_labels = None
628    xdata_labels = None
629    HAS_FLUXES = False
630    HAS_SPECIES = False
631    HAS_RULES = False
632    HAS_XDATA = False
633    IS_VALID = True
634
635    ##  def setLabels(self, species=None, fluxes=None, rules=None):
636    ##  """set the species, rate and rule label lists"""
637    ##  if species != None:
638    ##  self.species_labels = species
639    ##  if fluxes != None:
640    ##  self.flux_labels = fluxes
641    ##  if rules != None:
642    ##  self.rules_labels = rules
643
644    def setSpecies(self, species, lbls=None):
645        """Set the species array"""
646        self.species = species
647        self.HAS_SPECIES = True
648        if lbls != None:
649            self.species_labels = lbls
650            for s in range(len(self.species_labels)):
651                setattr(self, self.species_labels[s], self.species[s])
652
653    def setFluxes(self, fluxes, lbls=None):
654        """set the flux array"""
655        self.fluxes = fluxes
656        self.HAS_FLUXES = True
657        if lbls != None:
658            self.flux_labels = lbls
659            for f in range(len(self.flux_labels)):
660                setattr(self, self.flux_labels[f], self.fluxes[f])
661
662    def setRules(self, rules, lbls=None):
663        """Set the results of rate rules"""
664        self.rules = rules
665        self.HAS_RULES = True
666        if lbls != None:
667            self.rules_labels = lbls
668            for r in range(len(self.rules_labels)):
669                setattr(self, self.rules_labels[r], self.rules[r])
670
671    def setXData(self, xdata, lbls=None):
672        """Sets extra simulation data"""
673        self.xdata = xdata
674        self.HAS_XDATA = True
675        if lbls != None:
676            self.xdata_labels = lbls
677            for x in range(len(self.xdata_labels)):
678                setattr(self, self.xdata_labels[x], self.xdata[x])
679
680    def getSpecies(self, lbls=False):
681        """return species array"""
682        output = None
683        if self.HAS_SPECIES:
684            output = self.species
685        if not lbls:
686            return output
687        else:
688            return output, self.species_labels
689
690    def getFluxes(self, lbls=False):
691        """return flux array"""
692        output = None
693        if self.HAS_FLUXES:
694            output = self.fluxes
695        if not lbls:
696            return output
697        else:
698            return output, self.flux_labels
699
700    def getRules(self, lbls=False):
701        """Return rule array"""
702        output = None
703        if self.HAS_RULES:
704            output = self.rules
705        if not lbls:
706            return output
707        else:
708            return output, self.rules_labels
709
710    def getXData(self, lbls=False):
711        """Return xdata array"""
712        output = None
713        if self.HAS_XDATA:
714            output = self.xdata
715        if not lbls:
716            return output
717        else:
718            return output, self.xdata_labels
719
720    def getAllStateData(self, lbls=False):
721        """
722        Return all available data as species+fluxes+rules
723        if lbls=True returns (array,labels) else just array
724        """
725        labels = []
726        output = None
727        if self.HAS_SPECIES:
728            output = self.species
729            labels += self.species_labels
730        if self.HAS_FLUXES:
731            if output is None:
732                output = self.fluxes
733            else:
734                output = numpy.hstack((output, self.fluxes))
735            labels += self.flux_labels
736        if self.HAS_RULES:
737            if output is None:
738                output = self.rules
739            else:
740                output = numpy.hstack((output, self.rules))
741            labels += self.rules_labels
742        if self.HAS_XDATA:
743            if output is None:
744                output = self.xdata
745            else:
746                output = numpy.hstack((output, self.xdata))
747            labels += self.xdata_labels
748        if not lbls:
749            return output
750        else:
751            return output, labels
752
753    def getStateData(self, *args, **kwargs):
754        """getSimData(\*args) feed this method species/rate labels and it
755        will return an array of [time, sp1, r1, ....]
756        """
757
758        if 'lbls' in kwargs:
759            lbls = kwargs['lbls']
760        else:
761            lbls = False
762        lout = []
763        output = []
764        for roc in args:
765            if self.HAS_SPECIES and roc in self.species_labels:
766                lout.append(roc)
767                output.append(self.species[self.species_labels.index(roc)])
768            elif self.HAS_FLUXES and roc in self.flux_labels:
769                lout.append(roc)
770                output.append(self.fluxes[self.flux_labels.index(roc)])
771            elif self.HAS_RULES and roc in self.rules_labels:
772                lout.append(roc)
773                output.append(self.rules[self.rules_labels.index(roc)])
774            elif self.HAS_XDATA and roc in self.xdata_labels:
775                lout.append(roc)
776                output.append(self.xdata[self.xdata_labels.index(roc)])
777            else:
778                print('I don\'t have an attribute {} ... ignoring.'.format(roc))
779        if not lbls:
780            return output
781        else:
782            return numpy.array(output), lout
783
784
785class IntegrationDataObj(object):
786    """
787    This class is specifically designed to store the results of a time simulation
788    It has methods for setting the Time, Labels, Species and Rate data and
789    getting Time, Species and Rate (including time) arrays. However, of more use:
790
791    - getOutput(\*args) feed this method species/rate labels and it will return
792      an array of [time, sp1, r1, ....]
793    - getDataAtTime(time) the data generated at time point "time".
794    - getDataInTimeInterval(time, bounds=None) more intelligent version of the above
795      returns an array of all data points where: time-bounds <= time <= time+bounds
796    """
797
798    time = None
799    rates = None
800    species = None
801    rules = None
802    xdata = None
803    time_label = 'Time'
804    rate_labels = None
805    species_labels = None
806    rules_labels = None
807    xdata_labels = None
808    HAS_SPECIES = False
809    HAS_RATES = False
810    HAS_RULES = False
811    HAS_TIME = False
812    HAS_XDATA = False
813    IS_VALID = True
814    TYPE_INFO = 'Deterministic'
815
816    def setLabels(self, species=None, rates=None, rules=None):
817        """set the species, rate and rule label lists"""
818        if species != None:
819            self.species_labels = species
820        if rates != None:
821            self.rate_labels = rates
822        if rules != None:
823            self.rules_labels = rules
824
825    def setTime(self, time, lbl=None):
826        """Set the time vector"""
827        self.time = time.reshape(len(time), 1)
828        self.HAS_TIME = True
829        if lbl != None:
830            self.time_label = lbl
831
832    def setSpecies(self, species, lbls=None):
833        """Set the species array"""
834        self.species = species
835        self.HAS_SPECIES = True
836        if lbls != None:
837            self.species_labels = lbls
838
839    def setRates(self, rates, lbls=None):
840        """set the rate array"""
841        self.rates = rates
842        self.HAS_RATES = True
843        if lbls != None:
844            self.rate_labels = lbls
845
846    def setRules(self, rules, lbls=None):
847        """Set the results of rate rules"""
848        self.rules = rules
849        self.HAS_RULES = True
850        if lbls != None:
851            self.rules_labels = lbls
852
853    def setXData(self, xdata, lbls=None):
854        """Sets extra simulation data"""
855        self.xdata = xdata
856        self.HAS_XDATA = True
857        if lbls != None:
858            self.xdata_labels = lbls
859
860    def getTime(self, lbls=False):
861        """return the time vector"""
862        output = None
863        if self.HAS_TIME:
864            output = self.time.reshape(len(self.time),)
865        if not lbls:
866            return output
867        else:
868            return output, [self.time_label]
869
870    def getSpecies(self, lbls=False):
871        """return time+species array"""
872        output = None
873        if self.HAS_SPECIES:
874            output = numpy.hstack((self.time, self.species))
875            labels = [self.time_label] + self.species_labels
876        else:
877            output = self.time
878            labels = [self.time_label]
879        if not lbls:
880            return output
881        else:
882            return output, labels
883
884    def getRates(self, lbls=False):
885        """return time+rate array"""
886        output = None
887        if self.HAS_RATES:
888            output = numpy.hstack((self.time, self.rates))
889            labels = [self.time_label] + self.rate_labels
890        else:
891            output = self.time
892            labels = [self.time_label]
893        if not lbls:
894            return output
895        else:
896            return output, labels
897
898    def getRules(self, lbls=False):
899        """Return time+rule array"""
900        ##  assert self.rules != None, "\nNo rules"
901        output = None
902        if self.HAS_RULES:
903            output = numpy.hstack((self.time, self.rules))
904            labels = [self.time_label] + self.rules_labels
905        else:
906            output = self.time
907            labels = [self.time_label]
908        if not lbls:
909            return output
910        else:
911            return output, labels
912
913    def getXData(self, lbls=False):
914        """Return time+xdata array"""
915        ##  assert self.rules != None, "\nNo rules"
916        output = None
917        if self.HAS_XDATA:
918            output = numpy.hstack((self.time, self.xdata))
919            labels = [self.time_label] + self.xdata_labels
920        else:
921            output = self.time
922            labels = [self.time_label]
923        if not lbls:
924            return output
925        else:
926            return output, labels
927
928    def getDataAtTime(self, time):
929        """Return all data generated at "time" """
930        # TODO add rate rule data
931        t = None
932        sp = None
933        ra = None
934        ru = None
935        xd = None
936        temp_t = self.time.reshape(len(self.time),)
937        for tt in range(len(temp_t)):
938            if temp_t[tt] == time:
939                t = tt
940                if self.HAS_SPECIES:
941                    sp = self.species.take([tt], axis=0)
942                if self.HAS_RATES:
943                    ra = self.rates.take([tt], axis=0)
944                if self.HAS_RULES:
945                    ru = self.rules.take([tt], axis=0)
946                if self.HAS_XDATA:
947                    xd = self.xdata.take([tt], axis=0)
948                break
949
950        output = None
951        if t is not None:
952            output = numpy.array([[temp_t[t]]])
953            if sp is not None:
954                output = numpy.hstack((output, sp))
955            if ra is not None:
956                output = numpy.hstack((output, ra))
957            if ru is not None:
958                output = numpy.hstack((output, ru))
959            if xd is not None:
960                output = numpy.hstack((output, xd))
961
962        return output
963
964    def getDataInTimeInterval(self, time, bounds=None):
965        """
966         getDataInTimeInterval(time, bounds=None) returns an array of all
967         data points where: time-bounds <= time <= time+bounds
968         where bound defaults to stepsize
969        """
970        # TODO add rate rule data
971        temp_t = self.time.reshape(len(self.time),)
972        if bounds == None:
973            bounds = temp_t[1] - temp_t[0]
974        c1 = temp_t >= time - bounds
975        c2 = temp_t <= time + bounds
976        print('Searching ({}:{}:{})'.format(time - bounds, time, time + bounds))
977
978        t = []
979        sp = None
980        ra = None
981
982        for tt in range(len(c1)):
983            if c1[tt] and c2[tt]:
984                t.append(tt)
985        output = None
986        if len(t) > 0:
987            output = self.time.take(t)
988            output = output.reshape(len(output), 1)
989            if self.HAS_SPECIES and self.HAS_TIME:
990                output = numpy.hstack((output, self.species.take(t, axis=0)))
991            if self.HAS_RATES:
992                output = numpy.hstack((output, self.rates.take(t, axis=0)))
993            if self.HAS_RULES:
994                output = numpy.hstack((output, self.rules.take(t, axis=0)))
995            if self.HAS_XDATA:
996                output = numpy.hstack((output, self.xdata.take(t, axis=0)))
997        return output
998
999    def getOutput(self, *args, **kwargs):
1000        """
1001        Old alias for getSimData()
1002        getOutput(\*args) feed this method species/rate labels and it
1003        will return an array of [time, sp1, r1, ....]
1004        """
1005        return self.getSimData(*args, **kwargs)
1006
1007    def getAllSimData(self, lbls=False):
1008        """
1009        Return all available data as time+species+rates+rules
1010        if lbls=True returns (array,lables) else just array
1011        """
1012        labels = [self.time_label]
1013        if self.HAS_SPECIES and self.HAS_TIME:
1014            output = numpy.hstack((self.time, self.species))
1015            labels += self.species_labels
1016        if self.HAS_RATES:
1017            output = numpy.hstack((output, self.rates))
1018            labels += self.rate_labels
1019        if self.HAS_RULES:
1020            output = numpy.hstack((output, self.rules))
1021            labels += self.rules_labels
1022        if self.HAS_XDATA:
1023            output = numpy.hstack((output, self.xdata))
1024            labels += self.xdata_labels
1025        if not lbls:
1026            return output
1027        else:
1028            return output, labels
1029
1030    def getSimData(self, *args, **kwargs):
1031        """getSimData(\*args) feed this method species/rate labels and it
1032        will return an array of [time, sp1, r1, ....]
1033        """
1034        output = self.time
1035        ##  print argimrgs
1036        if 'lbls' in kwargs:
1037            lbls = kwargs['lbls']
1038        else:
1039            lbls = False
1040        lout = [self.time_label]
1041        for roc in args:
1042            if self.HAS_SPECIES and roc in self.species_labels:
1043                lout.append(roc)
1044                output = numpy.hstack(
1045                    (
1046                        output,
1047                        self.species.take([self.species_labels.index(roc)], axis=-1),
1048                    )
1049                )
1050            if self.HAS_RATES and roc in self.rate_labels:
1051                lout.append(roc)
1052                output = numpy.hstack(
1053                    (output, self.rates.take([self.rate_labels.index(roc)], axis=-1))
1054                )
1055            if self.HAS_RULES and roc in self.rules_labels:
1056                lout.append(roc)
1057                output = numpy.hstack(
1058                    (output, self.rules.take([self.rules_labels.index(roc)], axis=-1))
1059                )
1060            if self.HAS_XDATA and roc in self.xdata_labels:
1061                lout.append(roc)
1062                output = numpy.hstack(
1063                    (output, self.xdata.take([self.xdata_labels.index(roc)], axis=-1))
1064                )
1065        if not lbls:
1066            return output
1067        else:
1068            return output, lout
1069
1070
1071# this must stay in sync with core2
1072class NewCoreBase(object):
1073    """
1074    Core2 base class, needed here as we use Core2 derived classes
1075    in PySCes
1076    """
1077
1078    name = None
1079    __DEBUG__ = False
1080
1081    def getName(self):
1082        return self.name
1083
1084    def setName(self, name):
1085        self.name = name
1086
1087    def get(self, attr):
1088        """Return an attribute whose name is str(attr)"""
1089        return self.__getattribute__(attr)
1090
1091
1092# this must stay in sync with core2
1093class NumberBase(NewCoreBase):
1094    """
1095    Derived Core2 number class.
1096    """
1097
1098    value = None
1099    value_initial = None
1100
1101    def __call__(self):
1102        return self.value
1103
1104    def getValue(self):
1105        return self.value
1106
1107    def setValue(self, v):
1108        self.value = v
1109
1110
1111# Finally killed my lambda functions - brett07
1112class ReactionObj(NewCoreBase):
1113    """
1114    Defines a reaction with a KineticLaw *kl8, *formula* and *name* bound
1115    to a model instance, *mod*.
1116    """
1117
1118    formula = None
1119    mod = None
1120    rate = None
1121    xcode = None
1122    code_string = None
1123    symbols = None
1124    compartment = None
1125    piecewises = None
1126
1127    def __init__(self, mod, name, kl, klrepl='self.'):
1128        """
1129        mod : model instance
1130        name = reaction name
1131        kl = kinetic law
1132        klrepl = replacement string for KineticLaw
1133        """
1134        self.mod = mod
1135        self.name = name
1136        self.setKineticLaw(kl, klrepl='self.')
1137
1138    def __call__(self, *args):
1139        exec(self.xcode)
1140        return self.rate
1141
1142    def setKineticLaw(self, kl, klrepl='self.'):
1143        InfixParser.setNameStr('self.mod.', '')
1144        InfixParser.parse(kl.replace(klrepl, ''))
1145        self.symbols = InfixParser.names
1146        self.piecewises = InfixParser.piecewises
1147        formula = InfixParser.output
1148        # this code has to stay together #
1149        for pw in InfixParser.piecewises:
1150            formula = formula.replace(
1151                'self.mod.{}'.format(pw), 'self.mod.{}()'.format(pw)
1152            )
1153        # this code has to stay together #
1154        self.code_string = 'self.rate={}'.format(formula)
1155        self.xcode = compile(self.code_string, 'Req: {}'.format(self.name), 'exec')
1156        self.formula = (
1157            kl.replace(klrepl, '')
1158            .replace('numpy.', '')
1159            .replace('math.', '')
1160            .replace('operator.', '')
1161        )
1162
1163
1164InfixParser = MyInfixParser()
1165InfixParser.buildlexer()
1166InfixParser.buildparser(
1167    debug=0, debugfile='infix.dbg', tabmodule='infix_tabmodule', outputdir=OUTPUT_DIR
1168)
1169InfixParser.setNameStr('self.', '')
1170os.chdir(CWD)
1171
1172# adapted from core2
1173class Function(NewCoreBase):
1174    """
1175    Function class ported from Core2 to enable the use of functions in PySCeS.
1176    """
1177
1178    formula = None
1179    code_string = None
1180    xcode = None
1181    value = None
1182    symbols = None
1183    argsl = None
1184    mod = None
1185    piecewises = None
1186    functions = None
1187
1188    def __init__(self, name, mod):
1189        self.name = name
1190        self.argsl = []
1191        self.functions = []
1192        self.mod = mod
1193
1194    def __call__(self, *args):
1195        for ar in range(len(args)):
1196            self.__setattr__(self.argsl[ar], args[ar])
1197        exec(self.xcode)
1198        return self.value
1199
1200    def setArg(self, var, value=None):
1201        self.__setattr__(var, value)
1202        self.argsl.append(var)
1203
1204    def addFormula(self, formula):
1205        self.formula = formula
1206        InfixParser.setNameStr('self.', '')
1207        InfixParser.SymbolReplacements = {'_TIME_': 'mod._TIME_'}
1208        InfixParser.parse(formula)
1209        self.piecewises = InfixParser.piecewises
1210        self.symbols = InfixParser.names
1211        self.functions = InfixParser.functions
1212        self.code_string = 'self.value={}'.format(InfixParser.output)
1213        self.xcode = compile(self.code_string, 'Func: {}'.format(self.name), 'exec')
1214
1215
1216# adapted from core2
1217class EventAssignment(NumberBase):
1218    """
1219    Event assignments are actions that are triggered by an event.
1220    Ported from Core2 to build an event handling framework fro PySCeS
1221    """
1222
1223    variable = None
1224    symbols = None
1225    formula = None
1226    code_string = None
1227    xcode = None
1228    mod = None
1229    piecewises = None
1230    __DEBUG__ = False
1231
1232    def __call__(self):
1233        setattr(self.mod, self.variable, self.value)
1234        if self.__DEBUG__:
1235            print('\tAssigning {} = {}'.format(self.variable, self.value))
1236        return True
1237
1238    def __init__(self, name, mod):
1239        self.setName(name)
1240        self.mod = mod
1241
1242    def setVariable(self, var):
1243        self.variable = var
1244
1245    def setFormula(self, formula):
1246        self.formula = formula
1247        InfixParser.setNameStr('self.mod.', '')
1248        InfixParser.SymbolReplacements = {'_TIME_': 'self.mod._TIME_'}
1249        InfixParser.parse(formula)
1250        self.piecewises = InfixParser.piecewises
1251        self.symbols = InfixParser.names
1252        self.code_string = 'self.value={}'.format(InfixParser.output)
1253        self.xcode = compile(self.code_string, 'EvAs: {}'.format(self.name), 'exec')
1254        ##  print '\t', self.name, self.code_string
1255
1256    def evaluateAssignment(self):
1257        exec(self.xcode)
1258
1259
1260# adapted from core2
1261class Event(NewCoreBase):
1262    """
1263    Events have triggers and fire EventAssignments when required.
1264    Ported from Core2.
1265    """
1266
1267    trigger = None
1268    delay = 0.0
1269    formula = None
1270    code_string = None
1271    xcode = None
1272    state0 = False
1273    state = False
1274    assignments = None
1275    _TIME_ = 0.0
1276    _ASS_TIME_ = 0.0
1277    _need_action = False
1278    _assign_now = False
1279    symbols = None
1280    _time_symbol = None
1281    piecewises = None
1282    mod = None
1283    __DEBUG__ = True
1284
1285    def __init__(self, name, mod):
1286        self.setName(name)
1287        self.assignments = []
1288        self.mod = mod
1289
1290    def __call__(self, time):
1291        self._TIME_ = time
1292        exec(self.xcode)
1293        ret = False
1294        if self.state0 and not self.state:
1295            self.state0 = self.state
1296        if not self.state0 and self.state:
1297            for ass in self.assignments:
1298                ass.evaluateAssignment()
1299            self.state0 = self.state
1300            self._need_action = True
1301            self._ASS_TIME_ = time + self.delay
1302            if self.__DEBUG__:
1303                print('\nevent {} is evaluating at {}'.format(self.name, time))
1304            ret = True
1305        if self._need_action and self._TIME_ >= self._ASS_TIME_:
1306            self._assign_now = True
1307            if self.__DEBUG__:
1308                print(
1309                    'event {} is assigning at {} (delay={})'.format(
1310                        self.name, time, self.delay
1311                    )
1312                )
1313            self._need_action = False
1314            ret = True
1315        return ret
1316
1317    def setTrigger(self, formula, delay=0.0):
1318        self.formula = formula
1319        self.delay = delay
1320        InfixParser.setNameStr('self.mod.', '')
1321        if self._time_symbol != None:
1322            InfixParser.SymbolReplacements = {self._time_symbol: '_TIME_'}
1323        else:
1324            InfixParser.SymbolReplacements = {'_TIME_': '_TIME_'}
1325        InfixParser.parse(formula)
1326        self.piecewises = InfixParser.piecewises
1327        self.symbols = InfixParser.names
1328        self.code_string = 'self.state={}'.format(InfixParser.output)
1329        self.xcode = compile(self.code_string, 'Ev: {}'.format(self.name), 'exec')
1330
1331    def setAssignment(self, var, formula):
1332        ass = EventAssignment(var, mod=self.mod)
1333        ass.setVariable(var)
1334        ass.setFormula(formula)
1335        self.assignments.append(ass)
1336        self.__setattr__('_' + var, ass)
1337
1338    def reset(self):
1339        self.state0 = False
1340        self.state = False
1341        self._TIME_ = 0.0
1342        self._ASS_TIME_ = 0.0
1343
1344
1345class PieceWise(NewCoreBase):
1346    """
1347    Generic piecewise class adapted from Core2 that generates a compiled
1348    Python code block that allows evaluation of arbitrary length piecewise
1349    functions. Piecewise statements should be defined in assignment rules
1350    as `piecewise(<Piece>, <Conditional>, <OtherValue>)` where there can
1351    be an arbitrary number of `<Piece>, <Conditional>` pairs.
1352
1353    - *args* a dictionary of piecewise information generated by the InfixParser as InfixParser.piecewises
1354
1355    """
1356
1357    name = None
1358    value = None
1359    formula = None
1360    code_string = None
1361    xcode = None
1362    _names = None
1363    _TIME_ = None
1364
1365    def __init__(self, pwd, mod):
1366        pwd = pwd.copy()
1367        self.mod = mod
1368        if pwd['other'] != None:
1369            other = 'self.value = {}'.format(
1370                pwd.pop('other').replace('self.', 'self.mod.')
1371            )
1372        else:
1373            other = 'pass'
1374            pwd.pop('other')
1375
1376        InfixParser.setNameStr('self.mod.', '')
1377        self._names = []
1378        if len(list(pwd.keys())) == 1:
1379            formula = pwd[0][0]
1380            InfixParser.parse(formula)
1381            for n in InfixParser.names:
1382                if n not in self._names and n != '_TIME_':
1383                    self._names.append(n)
1384            formula = InfixParser.output
1385            thenStat = pwd[0][1].replace('self.', 'self.mod.')
1386            ##  thenStat = pwd[0][1]
1387            ##  InfixParser.setNameStr('self.mod.', '')
1388            ##  InfixParser.parse(thenStat)
1389            ##  thenStat = InfixParser.output
1390            self.code_string = 'if {}:\n    self.value = {}\nelse:\n    {}'.format(
1391                formula, thenStat, other,
1392            )
1393            self.formula = self.code_string.replace('self.', '')
1394        else:
1395            formula = pwd[0][0]
1396            InfixParser.parse(formula)
1397            for n in InfixParser.names:
1398                if n not in self._names and n != '_TIME_':
1399                    self._names.append(n)
1400            formula = InfixParser.output
1401            thenStat = pwd[0][1].replace('self.', 'self.mod.')
1402            ##  thenStat = pwd[0][1]
1403            ##  InfixParser.setNameStr('self.mod.', '')
1404            ##  InfixParser.parse(thenStat)
1405            ##  thenStat = InfixParser.output
1406            self.code_string = 'if {}:\n    self.value = {}\n'.format(formula, thenStat)
1407            pwd.pop(0)
1408            for p in pwd:
1409                formula = pwd[p][0]
1410                InfixParser.parse(formula)
1411                for n in InfixParser.names:
1412                    if n not in self._names and n != '_TIME_':
1413                        self._names.append(n)
1414
1415                formula = InfixParser.output
1416                thenStat = pwd[p][1].replace('self.', 'self.mod.')
1417                ##  thenStat = pwd[p][1]
1418                ##  InfixParser.setNameStr('self.mod.', '')
1419                ##  InfixParser.parse(thenStat)
1420                ##  thenStat = InfixParser.output
1421                self.code_string += 'elif {}:\n    self.value = {}\n'.format(
1422                    formula, thenStat,
1423                )
1424            self.code_string += 'else:\n    .format'.format(other)
1425            self.formula = self.code_string.replace('self.', '')
1426        self.xcode = compile(self.code_string, 'PieceWise', 'exec')
1427
1428    def __call__(self):
1429        exec(self.xcode)
1430        return self.value
1431
1432
1433class PysMod(object):
1434    """
1435    Create a model object and instantiate a PySCeS model so that it can be used for
1436    further analyses. PySCeS model descriptions can be loaded from files or strings
1437    (see the *loader* argument for details).
1438
1439    - *File* the name of the PySCeS input file if not explicit a \*.psc extension is
1440      assumed.
1441    - *dir* if specified, the path to the input file otherwise the default PyscesModel
1442      directory (defined in the pys_config.ini file) is assumed.
1443    - *autoload* autoload the model, pre 0.7.1 call mod.doLoad(). (default=True) **new**
1444    - *loader* the default behaviour is to load PSC file, however, if this argument is
1445      set to 'string' an input file can be supplied as the *fString* argument
1446      (default='file')
1447    - *fString* a string containing a PySCeS model file (use with *loader='string'*)
1448      the *File* argument now sepcifies the new input file name.
1449
1450    """
1451
1452    __version__ = __version__
1453    __pysces_directory__ = INSTALL_DIR
1454    __settings__ = None
1455    random = random
1456    # __STOMPY__ = None
1457
1458    def __init__(self, File=None, dir=None, loader='file', fString=None, autoload=True):
1459        """
1460        Create a model object and instantiate a PySCeS model so that it can be used for further analyses. PySCeS
1461        model descriptions can be loaded from files or strings (see the *loader* argument for details).
1462
1463        - *File* the name of the PySCeS input file if not explicit a \*.psc extension is assumed.
1464        - *dir* if specified, the path to the input file otherwise the default PyscesModel directory (defined in the pys_config.ini file) is assumed.
1465        - *autoload* autoload the model, pre 0.7.1 call mod.doLoad(). (default=True) **new**
1466        - *loader* the default behaviour is to load PSC file, however, if this argument is set to 'string' an input file can be supplied as the *fString* argument (default='file')
1467        - *fString* a string containing a PySCeS model file (use with *loader='string'*) the *File* argument now specifies the new input file name.
1468
1469        """
1470
1471        # if _HAVE_STOMPY:
1472        # self.__STOMPY__ = StomPyInterface(MODEL_DIR, OUTPUT_DIR)
1473        # print 'PySCeS/StochPy interface active'
1474        # else:
1475        # self.__STOMPY__ = None
1476
1477        self.__settings__ = {}
1478        self.__settings__.update({'enable_deprecated_attr': True})
1479        self.WorkDir = CWD
1480        if loader == 'file':
1481            self.LoadFromFile(File, dir)
1482        elif loader == 'string':
1483            self.LoadFromString(File, fString)
1484        else:
1485            self.LoadFromFile(File, dir)
1486        # stuff that needs to be done before initmodel
1487        self.__settings__['mode_substitute_assignment_rules'] = False
1488        self.__settings__['display_compartment_warnings'] = False
1489        self._TIME_ = 0.0  # this will be the built-in time
1490        self.piecewise_functions = []
1491        self.__piecewises__ = {}
1492        self.__HAS_PIECEWISE__ = False
1493        if autoload:
1494            self.ModelLoad()
1495            self.__PSC_auto_load = True
1496        else:
1497            self.__PSC_auto_load = False
1498
1499    def ModelLoad(self, stoich_load=0):
1500        """
1501        Load and instantiate a PySCeS model so that it can be used for further analyses. This function
1502        replaces the pre-0.7.1 doLoad() method.
1503
1504        - *stoich_load* try to load a structural analysis saved with Stoichiometry_Save_Serial() (default=0)
1505
1506        """
1507        self.InitialiseInputFile()
1508        assert self.__parseOK, '\nError in input file, parsing could not complete'
1509        self.Stoichiometry_Analyse(override=0, load=stoich_load)
1510        # add new Style functions to model
1511        self.InitialiseFunctions()
1512        self.InitialiseCompartments()
1513        self.InitialiseRules()
1514        self.InitialiseEvents()
1515        self.InitialiseModel()
1516        self.InitialiseRuleChecks()
1517        self.InitialiseOldFunctions()  # TODO replace this with initialisation functions
1518
1519    def doLoad(self, stoich_load=0):
1520        """
1521        Load and instantiate a PySCeS model so that it can be used for further analyses. This function is
1522        being replaced by the ModelLoad() method.
1523
1524        - *stoich_load* try to load a structural analysis saved with Stoichiometry_Save_Serial() (default=0)
1525
1526        """
1527        if not self.__PSC_auto_load:
1528            self.ModelLoad(stoich_load=stoich_load)
1529        else:
1530            print(
1531                'PySCeS now automatically loads the model on model object instantiation. If you do not want this behaviour pass the autoload=False argument to the constructor, if you really want to reload the model, run reLoad().'
1532            )
1533
1534    def reLoad(self, stoich_load=0):
1535        """
1536        Re-load and instantiate a PySCeS model so that it can be used for further analyses. This is just a convenience call to the ModelLoad() method.
1537
1538        - *stoich_load* try to load a structural analysis saved with Stoichiometry_Save_Serial() (default=0)
1539
1540        """
1541        self.ModelLoad(stoich_load=stoich_load)
1542
1543    def LoadFromString(self, File=None, fString=None):
1544        """
1545        Docstring required
1546        """
1547
1548        # grab model directory
1549        chkmdir()
1550        mdir = MODEL_DIR
1551
1552        # check for .psc extension
1553        File = chkpsc(File)
1554
1555        print('Using model directory: ' + mdir)
1556
1557        if not os.path.isdir(os.path.join(MODEL_DIR, "orca")):
1558            os.mkdir(os.path.join(MODEL_DIR, "orca"))
1559
1560        mdir = os.path.join(MODEL_DIR, "orca")
1561
1562        # write string to file
1563        try:
1564            outFile = open(os.path.join(mdir, File), 'w')
1565            outFile.write(fString)
1566            outFile.close()
1567            print('Using file: ' + File)
1568
1569            if os.path.exists(os.path.join(mdir, File)):
1570                print(os.path.join(mdir, File) + ' loading .....', end=' ')
1571                self.ModelDir = mdir
1572                self.ModelFile = File
1573            else:
1574                print(os.path.join(mdir, File) + ' does not exist')
1575                print('Please set with ModelDir and ModelFile .....', end=' ')
1576                self.ModelFile = 'None'
1577                self.ModelDir = mdir
1578        except Exception as e:
1579            print(e)
1580            print(
1581                os.path.join(mdir, File)
1582                + ' does not exist please re-instantiate model .....',
1583                end=' ',
1584            )
1585        self.__settings__['display_debug'] = 0
1586        self.ModelOutput = OUTPUT_DIR
1587
1588        # Initialize serial directory
1589        self.__settings__['serial_dir'] = os.path.join(self.ModelOutput, 'pscdat')
1590        # Initialize stoichiometric precision
1591        self.__settings__['stoichiometric_analysis_fp_zero'] = mach_spec.eps * 2.0e4
1592        self.__settings__['stoichiometric_analysis_lu_precision'] = self.__settings__[
1593            'stoichiometric_analysis_fp_zero'
1594        ]
1595        self.__settings__['stoichiometric_analysis_gj_precision'] = (
1596            self.__settings__['stoichiometric_analysis_lu_precision'] * 10.0
1597        )
1598
1599        """
1600        # Initialise elementary modes
1601        if os.sys.platform == 'win32':
1602            self.eModeExe_int = os.path.join(self.__metatool,'meta43_int.exe')
1603                self.eModeExe_dbl = os.path.join(self.__metatool,'meta43_double.exe')
1604        else:
1605            self.eModeExe_int = os.path.join(self.__metatool,'meta43_int')
1606            self.eModeExe_dbl = os.path.join(self.__metatool,'meta43_double')
1607        print 'Done.'
1608        """
1609
1610    def LoadFromFile(self, File=None, dir=None):
1611        """
1612        __init__(File=None,dir=None)
1613
1614        Initialise a PySCeS model object with PSC file that can be found in optional directory.
1615        If a a filename is not supplied the pysces.model_dir directory contents is displayed and
1616        the model name can be entered at the promp (<ctrl>+C exits the loading process).
1617
1618        Arguments:
1619
1620        File [default=None]: the name of the PySCeS input file
1621        dir [default=pysces.model_dir]: the optional directory where the PSC file can be found
1622
1623        """
1624        if dir != None:
1625            if os.path.isdir(dir):
1626                pass
1627            else:
1628                chkmdir()
1629                dir = MODEL_DIR
1630        else:
1631            chkmdir()
1632            dir = MODEL_DIR
1633
1634        mfgo = 0
1635        if File == None:
1636            mfgo = 1
1637        try:
1638            if File != None:
1639                File = chkpsc(File)
1640            if not os.path.exists(os.path.join(dir, File)):
1641                mfgo = 1
1642        except:
1643            mfgo = 1
1644
1645        while mfgo == 1:
1646            print('Models available in your model_dir: \n************')
1647            cntr = 0
1648            namelen = 0
1649            while len(os.listdir(dir)) == 0:
1650                print(
1651                    'No models available in model directory, please set using pys_usercfg.ini or call\
1652                with pysces.model(\'file.psc\',dir=\'path\\to\\models\')'
1653                )
1654                dir = input('\nPlease enter full path to models <CTRL+C> exits: ')
1655
1656            dirList = os.listdir(dir)
1657            for x in range(len(dirList) - 1, -1, -1):
1658                if dirList[x][-4:] != '.psc':
1659                    a = dirList.pop(x)
1660
1661            for x in dirList:
1662                if len(x) > namelen:
1663                    namelen = len(x)
1664            for x in dirList:
1665                if cntr < 2:
1666                    print(x + ' ' * (namelen - len(x)), end=' ')
1667                    cntr += 1
1668                else:
1669                    print(x)
1670                    cntr = 0
1671            print('\n************\n')
1672            print('\nYou need to specify a valid model file ...\n')
1673
1674            File = input('\nPlease enter filename: ')
1675            try:
1676                File = chkpsc(File)
1677                if os.path.exists(os.path.join(dir, File)):
1678                    mfgo = 0
1679            except:
1680                mfgo = 1
1681
1682        print('Using model directory: ' + dir)
1683
1684        try:
1685            if os.path.exists(os.path.join(dir, File)):
1686                print(os.path.join(dir, File) + ' loading .....', end=' ')
1687                self.ModelDir = dir
1688                self.ModelFile = File
1689            else:
1690                print(os.path.join(dir, File) + ' does not exist')
1691                print('Please set with ModelDir and ModelFile .....', end=' ')
1692                self.ModelFile = 'None'
1693                self.ModelDir = dir
1694        except:
1695            print(
1696                os.path.join(dir, File)
1697                + ' does not exist please re-instantiate model .....',
1698                end=' ',
1699            )
1700        self.__settings__['display_debug'] = 0
1701        self.ModelOutput = OUTPUT_DIR
1702
1703        ##  # Initialise elementary modes
1704        ##  if os.sys.platform == 'win32':
1705        ##  self.eModeExe_int = os.path.join(self.__metatool,'meta43_int.exe')
1706        ##  self.eModeExe_dbl = os.path.join(self.__metatool,'meta43_double.exe')
1707        ##  else:
1708        ##  self.eModeExe_int = os.path.join(self.__metatool,'meta43_int')
1709        ##  self.eModeExe_dbl = os.path.join(self.__metatool,'meta43_double')
1710        ##  print 'Done.'
1711
1712        # Initialize serial directory
1713        self.__settings__['serial_dir'] = os.path.join(self.ModelOutput, 'pscdat')
1714        # Initialize stoichiometric precision
1715        self.__settings__['stoichiometric_analysis_fp_zero'] = mach_spec.eps * 2.0e4
1716        self.__settings__['stoichiometric_analysis_lu_precision'] = self.__settings__[
1717            'stoichiometric_analysis_fp_zero'
1718        ]
1719        self.__settings__['stoichiometric_analysis_gj_precision'] = (
1720            self.__settings__['stoichiometric_analysis_lu_precision'] * 10.0
1721        )
1722
1723    # def __LoadStomPyInterface__(self):
1724    # """
1725    # Load the StomPy Stochastic simulation interface
1726    # """
1727    # if _HAVE_STOMPY and self.__STOMPY__ == None:
1728    # self.__STOMPY__ = StomPyInterface(MODEL_DIR, OUTPUT_DIR)
1729    # print 'PySCeS/StomPy interface active'
1730
1731    def __ParsePiecewiseFunctions__(self, piecewises):
1732        """
1733        THIS IS HIGHLY EXPERIMENTAL! Takes the a piecewises dictionary
1734        and creates a piecewise object while substituting the object name
1735        in the formula string.
1736
1737        - *piecewises* a piecewise dictionary created by the InfixParser
1738
1739        """
1740        if piecewises != None and len(list(piecewises.keys())) > 0:
1741            self.__HAS_PIECEWISE__ = True
1742            for p in piecewises:
1743                print('Info: adding piecewise object: {}'.format(p))
1744                # if len(piecewises[p].keys()) == 2:
1745                # piecewises[p][0].reverse() # only for libsbml generated infix
1746                self.__piecewises__.update({p: piecewises[p]})
1747                P = PieceWise(piecewises[p], self)
1748                P.setName(p)
1749                self.piecewise_functions.append(P)
1750                setattr(self, p, P)
1751
1752    def InitialiseInputFile(self):
1753        """
1754        InitialiseInputFile()
1755
1756        Parse the input file associated with the PySCeS model instance and assign the basic model attributes
1757
1758        Arguments:
1759        None
1760
1761        """
1762        self.__parseOK = 1  # check that model has parsed ok?
1763        try:
1764            if os.path.exists(os.path.join(self.ModelDir, self.ModelFile)):
1765                pass
1766            else:
1767                print(
1768                    '\nInvalid self.ModelFile: '
1769                    + os.path.join(self.ModelDir, self.ModelFile)
1770                )
1771        except:
1772            print(
1773                'WARNING: Problem verifying: '
1774                + os.path.join(self.ModelDir, self.ModelFile)
1775            )
1776
1777        if self.ModelFile[-4:] == '.psc':
1778            pass
1779        else:
1780            print('Assuming extension is .psc')
1781            self.ModelFile += '.psc'
1782
1783        print('\nParsing file: {}'.format(os.path.join(self.ModelDir, self.ModelFile)))
1784
1785        pscParser.ParsePSC(self.ModelFile, self.ModelDir, self.WorkDir)
1786        print(' ')
1787
1788        badlist = pscParser.KeywordCheck(pscParser.ReactionIDs)
1789        badlist = pscParser.KeywordCheck(pscParser.Inits, badlist)
1790
1791        if len(badlist) != 0:
1792            print(
1793                '\n******************************\nPSC input file contains PySCeS keywords please rename them and reload:'
1794            )
1795            for item in badlist:
1796                print('   --> ' + item)
1797            print('******************************\n')
1798            self.__parseOK = 0
1799            # assert len(badlist) != 0, 'Keyword error, please check input file'
1800
1801        if self.__parseOK:
1802            # brett 2008
1803            InfixParser.__pwcntr__ = 0
1804            self.__nDict__ = pscParser.nDict.copy()
1805            self.__sDict__ = pscParser.sDict.copy()
1806            self.__pDict__ = pscParser.pDict.copy()
1807            self.__uDict__ = pscParser.uDict.copy()
1808
1809            # model attributes are now initialised here brett2008
1810            self.__InitDict__ = {}
1811            # set parameters and add to __InitDict__
1812            for p in list(self.__pDict__.keys()):
1813                setattr(self, self.__pDict__[p]['name'], self.__pDict__[p]['initial'])
1814                self.__InitDict__.update(
1815                    {self.__pDict__[p]['name']: self.__pDict__[p]['initial']}
1816                )
1817            # set species and add to __InitDict__ and set mod.Xi_init
1818            for s in list(self.__sDict__.keys()):
1819                setattr(self, self.__sDict__[s]['name'], self.__sDict__[s]['initial'])
1820                if not self.__sDict__[s]['fixed']:
1821                    setattr(
1822                        self,
1823                        self.__sDict__[s]['name'] + '_init',
1824                        self.__sDict__[s]['initial'],
1825                    )
1826                self.__InitDict__.update(
1827                    {self.__sDict__[s]['name']: self.__sDict__[s]['initial']}
1828                )
1829
1830            # setup keywords
1831            self.__KeyWords__ = pscParser.KeyWords.copy()
1832            if self.__KeyWords__['Modelname'] == None:
1833                self.__KeyWords__['Modelname'] = self.ModelFile.replace('.psc', '')
1834            if self.__KeyWords__['Description'] == None:
1835                self.__KeyWords__['Description'] = self.ModelFile.replace('.psc', '')
1836            # if SpeciesTypes undefined assume []
1837            if self.__KeyWords__['Species_In_Conc'] == None:
1838                self.__KeyWords__['Species_In_Conc'] = True
1839            # if OutputType is undefined assume it is the same as SpeciesType
1840            if self.__KeyWords__['Output_In_Conc'] == None:
1841                if self.__KeyWords__['Species_In_Conc']:
1842                    self.__KeyWords__['Output_In_Conc'] = True
1843                else:
1844                    self.__KeyWords__['Output_In_Conc'] = False
1845            if self.__KeyWords__['ModelType'] == None:
1846                self.__KeyWords__['ModelType'] = ['Deterministic']
1847            else:
1848                self.__KeyWords__['ModelType'] = tuple(
1849                    [t.strip() for t in self.__KeyWords__['ModelType'].split(',')]
1850                )
1851
1852            # we now check for modeltype, if it specified as stochastic check if stochpy is available
1853            # if self.__KeyWords__['ModelType'] == ['Stochastic']:
1854            # if _HAVE_STOMPY:
1855            # print 'INFO: This model suggests that it requires discrete simulation and StochPy is installed ... mod.doStochSim() and mod.doStochSimPlot() are available for use.'
1856            # else:
1857            # print 'INFO: This model suggests that it requires stochastic simulation and StochPy (stompy.sf.net) is not installed ... PySCeS will treat this model as continuous.'
1858
1859            # set the species type in sDict according to 'Species_In_Conc'
1860            for s in list(self.__sDict__.keys()):
1861                if not self.__KeyWords__['Species_In_Conc']:
1862                    self.__sDict__[s]['isamount'] = True
1863                else:
1864                    self.__sDict__[s]['isamount'] = False
1865
1866            # setup compartments
1867            self.__compartments__ = pscParser.compartments.copy()
1868            if len(list(self.__compartments__.keys())) > 0:
1869                self.__HAS_COMPARTMENTS__ = True
1870            else:
1871                self.__HAS_COMPARTMENTS__ = False
1872
1873            # no (self.)
1874            self.__fixed_species__ = copy.copy(pscParser.fixed_species)
1875            self.__species__ = copy.copy(pscParser.species)
1876            self.__parameters__ = copy.copy(pscParser.parameters)
1877            self.__reactions__ = copy.copy(pscParser.reactions)
1878            self.__modifiers__ = copy.copy(pscParser.modifiers)
1879            # Initialize exposed stuff
1880            self.fixed_species = tuple(pscParser.fixed_species)
1881            self.species = tuple(pscParser.species)
1882            self.parameters = tuple(pscParser.parameters)
1883            self.reactions = tuple(pscParser.reactions)
1884            self.modifiers = tuple(pscParser.modifiers)
1885
1886            # Add input file defined fuctions - brett 200500621
1887            # TODO deprecated
1888            # self._Function_time = copy.copy(pscParser.TimeFunc)
1889            self._Function_user = copy.copy(pscParser.UserFunc)
1890            self._Function_init = pscParser.InitFunc
1891
1892            self.__functions__ = pscParser.Functions.copy()
1893            self.__rules__ = pscParser.AssignmentRules.copy()
1894            self.__InitFuncs__ = pscParser.ModelInit.copy()
1895            self.__userfuncs__ = pscParser.UserFuncs.copy()
1896            self.__eDict__ = pscParser.Events.copy()
1897            self.__cbm_fluxbounds__ = pscParser.cbm_FluxBounds.copy()
1898            self.__cbm_objfuncs__ = pscParser.cbm_ObjectiveFunctions.copy()
1899            self.__cbm_userfluxconstraints__ = pscParser.cbm_UserFluxConstraints.copy()
1900            ##  if pscParser.ModelUsesNumpyFuncs:
1901            ##  print 'Numpy functions detected in kinetic laws.\n'
1902        else:
1903            print('\nERROR: model parsing error, please check input file.\n')
1904        # added in a check for model correctness and human error reporting (1=ok, 0=error)
1905        if len(pscParser.SymbolErrors) != 0:
1906            print('\nUndefined symbols:\n{}'.format(pscParser.SymbolErrors))
1907        if not pscParser.ParseOK:
1908            print(
1909                '\n\n*****\nModel parsing errors detected in input file '
1910                + self.ModelFile
1911                + '\n*****'
1912            )
1913            print('\nInput file errors')
1914            for error in pscParser.LexErrors:
1915                print(error)
1916                ##  try:
1917                ##  print error[0] + 'in line:\t' + str(error[1]) + ' ('+ error[2][:20] +' ...)'
1918                ##  except IndexError:
1919                ##  print 'Illegal character:', error.__repr__(), error.__str__()
1920            print('\nParser errors')
1921            for error in pscParser.ParseErrors:
1922                print(error)
1923                ##  try:
1924                ##  print error[0] + '- ' + error[2][:20]
1925                ##  except IndexError:
1926                ##  print 'Illegal character:', error
1927            assert pscParser.ParseOK == 1, 'Input File Error'
1928
1929    def InitialiseRules(self):
1930        # we need to detect different types of rules etc
1931        # defmod
1932        self.__HAS_FORCED_FUNCS__ = False
1933        self.__HAS_RATE_RULES__ = False
1934        rate_rules = {}
1935        assignment_rules = {}
1936        for ar in self.__rules__:
1937            if self.__rules__[ar]['type'] == 'assignment':
1938                self.__HAS_FORCED_FUNCS__ = True
1939                assignment_rules.update({ar: self.__rules__[ar]})
1940            elif self.__rules__[ar]['type'] == 'rate':
1941                self.__HAS_RATE_RULES__ = True
1942                rate_rules.update({ar: self.__rules__[ar]})
1943
1944        # THE NEW WAY (adds formula parsing for numpy functions etc)
1945        InfixParser.setNameStr('self.', '')
1946        self._NewRuleXCode = {}
1947        if (
1948            self.__HAS_FORCED_FUNCS__
1949            and not self.__settings__['mode_substitute_assignment_rules']
1950        ):
1951            code_string = ''
1952            all_names = []
1953            for ar in assignment_rules:
1954                name = assignment_rules[ar]['name']
1955                InfixParser.setNameStr('self.', '')
1956                InfixParser.parse(assignment_rules[ar]['formula'])
1957                assignment_rules[ar]['symbols'] = InfixParser.names
1958                formula = InfixParser.output
1959                # this code has to stay together #
1960                for pw in InfixParser.piecewises:
1961                    formula = formula.replace(
1962                        'self.{}'.format(pw), 'self.{}()'.format(pw)
1963                    )
1964                self.__ParsePiecewiseFunctions__(InfixParser.piecewises)
1965                # this code has to stay together #
1966                assignment_rules[ar]['code_string'] = formula
1967                all_names += InfixParser.names
1968
1969            keep = []
1970            rules = list(assignment_rules.keys())
1971            dep = rules
1972            while len(dep) > 0:
1973                dep = []
1974                indep = []
1975                for ar in rules:
1976                    if ar in all_names:
1977                        indep.append(ar)
1978                    else:
1979                        dep.append(ar)
1980                keep += dep
1981                rules = indep
1982            for ar in indep + keep:
1983                evalCode = 'self.{} = {}\n'.format(
1984                    assignment_rules[ar]['name'], assignment_rules[ar]['code_string'],
1985                )
1986                self._NewRuleXCode.update({assignment_rules[ar]['name']: evalCode})
1987                code_string += evalCode
1988
1989            print('\nAssignment rule(s) detected.')
1990            self._Function_forced = code_string
1991        elif (
1992            self.__HAS_FORCED_FUNCS__
1993            and self.__settings__['mode_substitute_assignment_rules']
1994        ):
1995            # here we substitute nested assignment rules
1996            InfixParser.setNameStr('self.', '')
1997            symbR = {}
1998            for ass in assignment_rules:
1999                InfixParser.setNameStr('self.', '')
2000                InfixParser.parse(assignment_rules[ass]['formula'])
2001                formula = InfixParser.output
2002                # this code has to stay together #
2003                for pw in InfixParser.piecewises:
2004                    formula = formula.replace(
2005                        'self.{}'.format(pw), 'self.{}()'.format(pw)
2006                    )
2007                self.__ParsePiecewiseFunctions__(InfixParser.piecewises)
2008                # this code has to stay together #
2009                symbR.update({assignment_rules[ass]['name']: formula})
2010            for ass in assignment_rules:
2011                InfixParser.setNameStr('self.', '')
2012                InfixParser.FunctionReplacements = symbR
2013                InfixParser.parse(assignment_rules[ass]['formula'])
2014                assignment_rules[ass]['code_string'] = InfixParser.output
2015            self._Function_forced = 'pass\n'
2016            print('Assignment rule(s) detected and substituted.')
2017        else:
2018            self._Function_forced = 'pass\n'
2019
2020        self.__CODE_forced = compile(self._Function_forced, 'AssignRules', 'exec')
2021        # tested in InitialiseRuleChecks()
2022
2023        # defmod
2024        self.__rate_rules__ = []
2025        self.__rrule__ = None
2026        rr_code_block = ''
2027        rr_map_block = ''
2028        self.__CODE_raterule = None
2029        self._NewRateRuleXCode = {}
2030        if self.__HAS_RATE_RULES__:
2031            # create a rr vector
2032            self.__rrule__ = numpy.ones(len(list(rate_rules.keys())), 'd')
2033            # brett2008 debug stuff doesn't do any harm
2034            cntr = 0
2035            rr_keys = list(rate_rules.keys())
2036            rr_keys.sort()
2037            for ar in rr_keys:
2038                name = rate_rules[ar]['name']
2039                InfixParser.setNameStr('self.', '')
2040                InfixParser.parse(rate_rules[ar]['formula'])
2041                formula = InfixParser.output
2042                rate_rules[ar]['symbols'] = InfixParser.names
2043                # this code has to stay together #
2044                for pw in InfixParser.piecewises:
2045                    formula = formula.replace(
2046                        'self.{}'.format(pw), 'self.{}()'.format(pw)
2047                    )
2048                self.__ParsePiecewiseFunctions__(InfixParser.piecewises)
2049                # this code has to stay together #
2050                rate_rules[ar]['code_string'] = formula
2051                self.__rate_rules__.append(name)
2052                rr_code_block += 'self.__rrule__[{}] = {}\n'.format(cntr, formula)
2053                ##  rr_code_block += 'self.%s = self.__rrule__[%s]\n' % (name, cntr)
2054                rr_map_block += 'self.{} = self.__rrule__[{}]\n'.format(name, cntr)
2055                cntr += 1
2056                # create mod.<rule name>_init attributes
2057                setattr(self, '{}_init'.format(name), getattr(self, name))
2058            print('Rate rule(s) detected.')
2059        else:
2060            rr_code_block = 'pass\n'
2061            rr_map_block = 'pass\n'
2062        # TODO consider putting this in self.__HAS_RATE_RULES__
2063        self.__CODE_raterule = compile(rr_code_block, 'RateRules', 'exec')
2064        self.__CODE_raterule_map = compile(rr_map_block, 'RateRuleMap', 'exec')
2065
2066        del rate_rules, assignment_rules
2067
2068    def InitialiseRuleChecks(self):
2069        try:
2070            exec(self.__CODE_raterule)
2071        except ZeroDivisionError:
2072            print(
2073                'WARNING: Assignment RateRule ZeroDivision on initialisation (continuing)'
2074            )
2075        except Exception as ex:
2076            print('WARNING: RateRule initialisation error\n', ex)
2077
2078        zeroDivErr = []
2079        for k in self._NewRuleXCode:
2080            try:
2081                exec(compile(self._NewRuleXCode[k], 'NewRuleXCode', 'exec'))
2082            except Exception as ex:
2083                zeroDivErr.append(k)
2084        exit = len(list(self._NewRuleXCode.keys()))
2085        while len(zeroDivErr) > 0 and exit > 0:
2086            zeroDivErr2 = []
2087            for kk in zeroDivErr:
2088                try:
2089                    exec(compile(self._NewRuleXCode[kk], 'NewRuleXCode', 'exec'))
2090                    if self.__HAS_RATE_RULES__:
2091                        exec(self.__CODE_raterule)
2092                        exec(self.__CODE_raterule_map)
2093                except Exception as ex:
2094                    zeroDivErr2.append(kk)
2095            exit -= 1
2096            zeroDivErr = zeroDivErr2
2097            if exit < 0:
2098                print('WARNING: ZeroDivision elimination failed')
2099
2100        try:
2101            exec(self.__CODE_forced)
2102            # print 'done.'
2103        except ZeroDivisionError:
2104            print('WARNING: Assignment rule ZeroDivision on intialisation')
2105        except Exception as ex:
2106            print('WARNING: Assignment rule error (disabling all rules)\n', ex)
2107            self.__CODE_forced = compile('pass\n', 'AssignRules', 'exec')
2108
2109    def Stoichiometry_Init(self, nmatrix, load=0):
2110        """
2111        Stoichiometry_Init(nmatrix,load=0)
2112
2113        Initialize the model stoichiometry. Given a stoichiometric matrix N, this method will return an instantiated PyscesStoich instance and status flag. Alternatively, if load is enabled, PySCeS will
2114        attempt to load a previously saved stoichiometric analysis (saved with Stoichiometry_Save_Serial)
2115        and test it's correctness. The status flag indicates 0 = reanalyse stoichiometry or
2116        1 = complete structural analysis preloaded.
2117
2118        Arguments:
2119
2120        nmatrix: The input stoichiometric matrix, N
2121        load [default=0]: try to load a saved stoichiometry (1)
2122
2123        """
2124        if load:
2125            print('Loading saved stoichiometry ...')
2126            try:
2127                stc = self.Stoichiometry_Load_Serial()
2128                go = 1
2129            except Exception as slx:
2130                print(slx)
2131                go = 0
2132
2133            row, col = nmatrix.shape
2134            if go:
2135                badList = []
2136                for x in range(row):
2137                    if stc.__species__[x] != self.__species__[x]:
2138                        badList.append((stc.__species__[x], self.__species__[x]))
2139                        go = 0
2140                    for y in range(col):
2141                        if stc.__reactions__[y] != self.__reactions__[y]:
2142                            badList.append(
2143                                (stc.__reactions__[y], self.__reactions__[y])
2144                            )
2145                            go = 0
2146                        if (
2147                            abs(nmatrix[x, y] - stc.nmatrix[x, y])
2148                            > stc.stoichiometric_analysis_fp_zero
2149                        ):
2150                            badList.append(((x, y), nmatrix[x, y], stc.nmatrix[x, y]))
2151                            go = 0
2152            if not go:
2153                ##  print 'Stoichiometry mismatch'
2154                ##  for x in badList:
2155                ##  print x,
2156                print('\nProblem loading stoichiometry, reanalysing ...')
2157                stc = PyscesStoich.Stoich(nmatrix)
2158                status = 0
2159            else:
2160                print('Stoichiometry verified ... we have liftoff')
2161                status = 1
2162        else:
2163            # print 'Instantiating new stoichiometry ...'
2164            stc = PyscesStoich.Stoich(nmatrix)
2165            status = 0
2166        return stc, status
2167
2168    def Stoichiometry_Save_Serial(self):
2169        """Serialize and save a Stoichiometric instance to binary pickle""" """
2170        Stoichiometry_Save_Serial()
2171
2172        Serilaise and save the current model stoichiometry to a file with name <model>_stoichiometry.pscdat
2173        in the mod.__settings__['serial_dir'] directory (default: mod.model_output/pscdat)
2174
2175        Arguments:
2176        None
2177
2178        """
2179        # new plan, I introduce species and reaction arrays into the stoich instance for verification purposes
2180        # brett - 20050831
2181        self.__structural__.__species__ = copy.copy(self.__species__)
2182        self.__structural__.__reactions__ = copy.copy(self.__reactions__)
2183        self.SerialEncode(self.STOICH, self.ModelFile[:-4] + '_stoichiometry')
2184
2185    def Stoichiometry_Load_Serial(self):
2186        """
2187        Stoichiometry_Load_Serial()
2188
2189        Load a saved stoichiometry saved with mod.Stoichiometry_Save_Serial() and return
2190        a stoichiometry instance.
2191
2192        Arguments:
2193        None
2194
2195        """
2196        stc = self.SerialDecode(self.ModelFile[:-4] + '_stoichiometry')
2197        return stc
2198
2199    def Stoichiometry_Analyse(self, override=0, load=0):
2200        """
2201        Stoichiometry_Analyse(override=0,load=0)
2202
2203        Perform a structural analyses. The default behaviour is to construct and analyse the model
2204        from the parsed model information. Overriding this behaviour analyses the stoichiometry
2205        based on the current stoichiometric matrix. If load is specified PySCeS tries to load a
2206        saved stoichiometry, otherwise the stoichiometric analysis is run. The results of
2207        the analysis are checked for floating point error and nullspace rank consistancy.
2208
2209        Arguments:
2210
2211        override [default=0]: override stoichiometric analysis intialisation from parsed data
2212        load [default=0]: load a presaved stoichiometry
2213
2214        """
2215        if not override:
2216            self.__nmatrix__ = self.__initmodel__()  # Creates the model N
2217            # print '\nintializing N\n'
2218        else:
2219            print('\nStoichiometric override active\n')
2220
2221        assert (
2222            len(self.__nmatrix__) > 0
2223        ), '\nUnable to generate Stoichiometric Matrix! model has:\n{} reactions\n{} species\nwhat did you have in mind?\n'.format(
2224            len(self.__reactions__), len(self.__species__)
2225        )
2226
2227        self.__Nshape__ = self.__nmatrix__.shape  # Get the shape of N
2228        ##  self.__Vtemp__ = numpy.zeros((self.__Nshape__[1])) # going going ....
2229
2230        # get stoich instance and whether it was analysed or loaded - brett 20050830
2231        self.__structural__, stc_load = self.Stoichiometry_Init(
2232            self.__nmatrix__, load=load
2233        )
2234
2235        # if not loaded analyze - brett 20050830
2236        if not stc_load:
2237            # technically this means we can define this on the fly - brett #20051013
2238            self.__structural__.stoichiometric_analysis_fp_zero = self.__settings__[
2239                'stoichiometric_analysis_fp_zero'
2240            ]
2241            self.__structural__.stoichiometric_analysis_lu_precision = self.__settings__[
2242                'stoichiometric_analysis_lu_precision'
2243            ]
2244            self.__structural__.stoichiometric_analysis_gj_precision = self.__settings__[
2245                'stoichiometric_analysis_gj_precision'
2246            ]
2247            self.__structural__.AnalyseL()  # Get all L related stuff
2248            self.__structural__.AnalyseK()  # Get all K related stuff
2249
2250        # test matrix values against __settings__['stoichiometric_analysis_lu_precision']
2251        lsmall, lbig = self.__structural__.MatrixValueCompare(
2252            self.__structural__.lzeromatrix
2253        )
2254        ksmall, kbig = self.__structural__.MatrixValueCompare(
2255            self.__structural__.kzeromatrix
2256        )
2257        SmallValueError = 0
2258        if (
2259            abs(lsmall)
2260            < self.__structural__.stoichiometric_analysis_lu_precision * 10.0
2261        ):
2262            print(
2263                '\nWARNING: values in L0matrix are close to stoichiometric precision!'
2264            )
2265            print(
2266                'Stoichiometric LU precision:',
2267                self.__structural__.stoichiometric_analysis_lu_precision,
2268            )
2269            print('L0 smallest abs(value)', abs(lsmall))
2270            print('Machine precision:', mach_spec.eps)
2271            SmallValueError = 1
2272        if (
2273            abs(ksmall)
2274            < self.__structural__.stoichiometric_analysis_lu_precision * 10.0
2275        ):
2276            print(
2277                '\nWARNING: values in K0matrix are close to stoichiometric precision!'
2278            )
2279            print(
2280                'Stoichiometric precision:',
2281                self.__structural__.stoichiometric_analysis_lu_precision,
2282            )
2283            print('K0 smallest abs(value)', abs(ksmall))
2284            print('Machine precision:', mach_spec.eps)
2285            SmallValueError = 1
2286        if SmallValueError:
2287            input(
2288                '\nStructural Analysis results may not be reliable!!!.\n\nTry change <mod>.__settings__["stoichiometric_analysis_lu_precision"] (see reference manual for details)\n\n\t press any key to continue: '
2289            )
2290
2291        # cross check that rank is consistant between K0 and L0
2292        if (
2293            self.__structural__.kzeromatrix.shape[0]
2294            != self.__structural__.lzeromatrix.shape[1]
2295        ):
2296            print(
2297                '\nWARNING: the rank calculated by the Kand L analysis methods are not the same!'
2298            )
2299            print(
2300                '\tK analysis calculates the rank as: '
2301                + repr(self.__structural__.kzeromatrix.shape[0])
2302            )
2303            print(
2304                '\tL analysis calculates the rank as: '
2305                + repr(self.__structural__.lzeromatrix.shape[1])
2306            )
2307            print('This is not good! Structural Analysis results are not reliable!!!\n')
2308            assert (
2309                self.__structural__.kzeromatrix.shape[0]
2310                == self.__structural__.lzeromatrix.shape[1]
2311            ), '\nStructuralAnalysis Error: rank mismatch'
2312
2313        self.__HAS_FLUX_CONSERVATION__ = self.__structural__.info_flux_conserve
2314        self.__HAS_MOIETY_CONSERVATION__ = self.__structural__.info_moiety_conserve
2315
2316        if self.__settings__['enable_deprecated_attr']:
2317            ##  self.__nmatrix__ = copy.copy(self.nmatrix)
2318            self.nmatrix = self.__nmatrix__  # done with caution brett2008
2319            self.nmatrix_row = self.__structural__.nmatrix_row
2320            self.nmatrix_col = self.__structural__.nmatrix_col
2321
2322            self.kmatrix = self.__structural__.kmatrix
2323            self.kmatrix_row = self.__structural__.kmatrix_row
2324            self.kmatrix_col = self.__structural__.kmatrix_col
2325            self.kzeromatrix = self.__structural__.kzeromatrix
2326            self.kzeromatrix_row = self.__structural__.kzeromatrix_row
2327            self.kzeromatrix_col = self.__structural__.kzeromatrix_col
2328
2329            self.lmatrix = self.__structural__.lmatrix
2330            self.lmatrix_row = self.__structural__.lmatrix_row
2331            self.lmatrix_col = self.__structural__.lmatrix_col
2332            self.lzeromatrix = self.__structural__.lzeromatrix
2333            self.lzeromatrix_row = self.__structural__.lzeromatrix_row
2334            self.lzeromatrix_col = self.__structural__.lzeromatrix_col
2335            self.conservation_matrix = self.__structural__.conservation_matrix
2336            self.conservation_matrix_row = self.__structural__.conservation_matrix_row
2337            self.conservation_matrix_col = self.__structural__.conservation_matrix_col
2338            self.nrmatrix = self.__structural__.nrmatrix
2339            self.nrmatrix_row = self.__structural__.nrmatrix_row
2340            self.nrmatrix_col = self.__structural__.nrmatrix_col
2341
2342            self.__kmatrix__ = copy.copy(self.kmatrix)
2343            self.__kzeromatrix__ = copy.copy(self.kzeromatrix)
2344            self.__lmatrix__ = copy.copy(self.lmatrix)
2345            self.__lzeromatrix__ = copy.copy(self.lzeromatrix)
2346            self.__nrmatrix__ = copy.copy(self.nrmatrix)
2347        # switch that is set if the stoichiometric analysis is up to date
2348
2349        self.__structural__.species = self.species
2350        self.__structural__.reactions = self.reactions
2351        self.Nmatrix = PyscesStoich.StructMatrix(
2352            self.__structural__.nmatrix,
2353            self.__structural__.nmatrix_row,
2354            self.__structural__.nmatrix_col,
2355        )
2356        self.Nmatrix.setRow(self.species)
2357        self.Nmatrix.setCol(self.reactions)
2358
2359        self.Nrmatrix = PyscesStoich.StructMatrix(
2360            self.__structural__.nrmatrix,
2361            self.__structural__.nrmatrix_row,
2362            self.__structural__.nrmatrix_col,
2363        )
2364        self.Nrmatrix.setRow(self.species)
2365        self.Nrmatrix.setCol(self.reactions)
2366
2367        self.Kmatrix = PyscesStoich.StructMatrix(
2368            self.__structural__.kmatrix,
2369            self.__structural__.kmatrix_row,
2370            self.__structural__.kmatrix_col,
2371        )
2372        self.Kmatrix.setRow(self.reactions)
2373        self.Kmatrix.setCol(self.reactions)
2374
2375        self.K0matrix = PyscesStoich.StructMatrix(
2376            self.__structural__.kzeromatrix,
2377            self.__structural__.kzeromatrix_row,
2378            self.__structural__.kzeromatrix_col,
2379        )
2380        self.K0matrix.setRow(self.reactions)
2381        self.K0matrix.setCol(self.reactions)
2382
2383        self.Lmatrix = PyscesStoich.StructMatrix(
2384            self.__structural__.lmatrix,
2385            self.__structural__.lmatrix_row,
2386            self.__structural__.lmatrix_col,
2387        )
2388        self.Lmatrix.setRow(self.species)
2389        self.Lmatrix.setCol(self.species)
2390
2391        self.L0matrix = PyscesStoich.StructMatrix(
2392            self.__structural__.lzeromatrix,
2393            self.__structural__.lzeromatrix_row,
2394            self.__structural__.lzeromatrix_col,
2395        )
2396        self.L0matrix.setRow(self.species)
2397        self.L0matrix.setCol(self.species)
2398
2399        if self.__structural__.info_moiety_conserve:
2400            self.Consmatrix = PyscesStoich.StructMatrix(
2401                self.__structural__.conservation_matrix,
2402                self.__structural__.conservation_matrix_row,
2403                self.__structural__.conservation_matrix_col,
2404            )
2405            self.Consmatrix.setRow(self.species)
2406            self.Consmatrix.setCol(self.species)
2407        else:
2408            self.Consmatrix = None
2409        self.__StoichOK = 1
2410        print(' ')
2411
2412    def Stoichiometry_ReAnalyse(self):
2413        """
2414        Stoichiometry_ReAnalyse()
2415
2416        Reanalyse the stoichiometry using the current N matrix ie override=1
2417        (for use with mod.Stoich_matrix_SetValue)
2418
2419        Arguments:
2420        None
2421
2422        """
2423        self.Stoichiometry_Analyse(override=1)
2424        self.InitialiseConservationArrays()
2425
2426    def Stoich_nmatrix_SetValue(self, species, reaction, value):
2427        """
2428        Stoich_nmatrix_SetValue(species,reaction,value)
2429
2430        Change a stoichiometric coefficient's value in the N matrix. Only a coefficients magnitude may be set, in other words a
2431        a coefficient's value must remain negative, positive or zero. After changing a coefficient
2432        it is necessary to Reanalyse the stoichiometry.
2433
2434        Arguments:
2435
2436        species: species name (s0)
2437        reaction: reaction name (R4)
2438        value: new coefficient value
2439
2440        """
2441        index = self.__Stoich_nmatrix_FindIndex__(species, reaction)
2442        if self.__Stoich_nmatrix_CheckValue__(index, value):
2443            self.__Stoich_nmatrix_UpdateValue__(index, value)
2444            self.__StoichOK = 0
2445
2446    def __Stoich_nmatrix_FindIndex__(self, species, reaction):
2447        """
2448        __Stoich_nmatrix_FindIndex__(species,reaction)
2449
2450        Return the N matrix co-ordinates of the coefficient referenced by species and reaction name.
2451
2452        Arguments:
2453
2454        species: species name
2455        reaction: reaction name
2456
2457        """
2458        rval = None
2459        cval = None
2460        for x in enumerate(self.species):
2461            if species == x[1]:
2462                rval = x[0]
2463        for y in enumerate(self.reactions):
2464            if reaction == y[1]:
2465                cval = y[0]
2466        return (rval, cval)
2467
2468    def __Stoich_nmatrix_UpdateValue__(self, xxx_todo_changeme, val):
2469        """
2470        __Stoich_nmatrix_UpdateValue__((x,y), val)
2471
2472        Update N matrix co-ordinates (x,y) with val
2473
2474        Arguments:
2475        (x,y): row, column coordinates
2476        val: value
2477
2478        """
2479        (x, y) = xxx_todo_changeme
2480        self.nmatrix[x, y] = val
2481        self.__nmatrix__[x, y] = val
2482        self.Nmatrix.array[x, y] = val
2483
2484    def __Stoich_nmatrix_CheckValue__(self, xxx_todo_changeme1, val):
2485        """
2486        __Stoich_nmatrix_CheckValue__((x,y),val)
2487
2488        Check validity of new coefficient value against existing N matrix coefficient (x,y) for existance and/or sign.
2489        Returns 1 (true) or 0.
2490
2491        Arguments:
2492        (x,y): N matrix coordinates
2493        val: new value
2494
2495        """
2496        (x, y) = xxx_todo_changeme1
2497        go = 1
2498        if x == None or y == None:
2499            print('\nSpecies (nmatrix) index  =', x)
2500            print('Reaction (nmatrix) index =', y)
2501            print(
2502                '\nI\'m confused, perhaps you entered an incorrect species or reaction name'
2503            )
2504            print('or they are in the wrong order?')
2505            go = 0
2506        elif abs(self.__nmatrix__[x, y]) == 0.0:
2507            if val != 0.0:
2508                go = 0
2509                print('\nZero coefficient violation')
2510                print(
2511                    '  nmatrix['
2512                    + repr(x)
2513                    + ','
2514                    + repr(y)
2515                    + '] can only be = 0.0 (input '
2516                    + str(val)
2517                    + ')'
2518                )
2519        elif self.__nmatrix__[x, y] > 0.0:
2520            if val <= 0.0:
2521                go = 0
2522                print('\nPositive coefficient violation')
2523                print(
2524                    '  nmatrix['
2525                    + repr(x)
2526                    + ','
2527                    + repr(y)
2528                    + '] can only be > 0.0 (input '
2529                    + str(val)
2530                    + ')'
2531                )
2532        elif self.__nmatrix__[x, y] < 0.0:
2533            if val >= 0.0:
2534                go = 0
2535                print('\nNegative coefficient violation')
2536                print(
2537                    '  nmatrix['
2538                    + repr(x)
2539                    + ','
2540                    + repr(y)
2541                    + '] can only be < 0.0 (input '
2542                    + str(val)
2543                    + ')'
2544                )
2545        if go:
2546            return 1
2547        else:
2548            return 0
2549
2550    def SerialEncode(self, data, filename):
2551        """
2552        SerialEncode(data,filename)
2553
2554        Serialise and save a Python object using a binary pickle to file. The serialised object
2555        is saved as <filename>.pscdat in the directory defined by mod.model_serial.
2556
2557        Arguments:
2558
2559        data: pickleable Python object
2560        filename: the ouput filename
2561
2562        """
2563        filename = str(filename) + '.pscdat'
2564        if os.path.exists(self.__settings__['serial_dir']):
2565            pass
2566        else:
2567            os.mkdir(self.__settings__['serial_dir'])
2568        print('\ncPickle data stored in: ' + self.__settings__['serial_dir'])
2569
2570        File = open(os.path.join(self.__settings__['serial_dir'], filename), 'wb')
2571        try:
2572            pickle.dump(data, File, -1)
2573            print('Serialization complete')
2574        except Exception as E:
2575            print('Serialize error:\n', E)
2576        File.flush()
2577        File.close()
2578        del data
2579
2580    def SerialDecode(self, filename):
2581        """
2582        SerialDecode(filename)
2583
2584        Decode and return a serialised object saved with SerialEncode.
2585
2586        Arguments:
2587
2588        filename: the filename (.pscdat is assumed)
2589
2590        """
2591        filename = str(filename) + '.pscdat'
2592        if not os.path.exists(os.path.join(self.__settings__['serial_dir'], filename)):
2593            raise RuntimeError(
2594                'Serialized data '
2595                + os.path.join(self.__settings__['serial_dir'], filename)
2596                + ' does not exist'
2597            )
2598        data = None
2599        try:
2600            File = open(os.path.join(self.__settings__['serial_dir'], filename), 'rb')
2601            data = pickle.load(File)
2602            File.close()
2603            print('Serial decoding complete')
2604        except Exception as E:
2605            print('Serial decode error:\n', E)
2606
2607        return data
2608
2609    def InitialiseCompartments(self):
2610        # the name should say it all
2611        self.__settings__['compartment_fudge_factor'] = 1.0
2612        startFudging = 1.0e-8
2613        I_AM_FUDGING = False
2614        if self.__HAS_COMPARTMENTS__:
2615            tmp = min(
2616                [
2617                    abs(self.__compartments__[c]['size'])
2618                    for c in list(self.__compartments__.keys())
2619                ]
2620            )
2621            if tmp < startFudging:
2622                ##  self.__settings__['compartment_fudge_factor'] = 10.0**round(numpy.log10(tmp))
2623                self.__settings__[
2624                    'compartment_fudge_factor'
2625                ] = tmp  # sneaky b*))_)$^&*@rds
2626                ##  print 'compartment_fudge_factor', self.__settings__['compartment_fudge_factor']
2627            for c in list(self.__compartments__.keys()):
2628                if self.__settings__['compartment_fudge_factor'] < startFudging:
2629                    newsize = (
2630                        self.__compartments__[c]['size']
2631                        / self.__settings__['compartment_fudge_factor']
2632                    )
2633                    print(
2634                        'INFO: Rescaling compartment with size {} to {}'.format(
2635                            self.__compartments__[c]['size'], newsize
2636                        )
2637                    )
2638                    self.__compartments__[c]['size'] = newsize
2639                    self.__compartments__[c].update(
2640                        {'scale': self.__settings__['compartment_fudge_factor']}
2641                    )
2642                    I_AM_FUDGING = True
2643                setattr(
2644                    self,
2645                    self.__compartments__[c]['name'],
2646                    self.__compartments__[c]['size'],
2647                )
2648                setattr(
2649                    self,
2650                    '{}_init'.format(self.__compartments__[c]['name']),
2651                    self.__compartments__[c]['size'],
2652                )
2653        if self.__HAS_COMPARTMENTS__:
2654            for sp in list(self.__sDict__.keys()):
2655                if (
2656                    self.__sDict__[sp]['compartment'] == None
2657                    and len(list(self.__compartments__.keys())) == 1
2658                ):
2659                    self.__sDict__[sp]['compartment'] = self.__compartments__[
2660                        list(self.__compartments__.keys())[0]
2661                    ]['name']
2662                    print(
2663                        'COMPARTMENT WARNING: this model has a compartment defined {} but \"{}\" is not in it ... I\'ll try it for now'.format(
2664                            [
2665                                self.__compartments__[c]['name']
2666                                for c in list(self.__compartments__.keys())
2667                            ],
2668                            sp,
2669                        )
2670                    )
2671                    ##  print self.__sDict__[sp]
2672                elif (
2673                    self.__sDict__[sp]['compartment'] == None
2674                    and len(list(self.__compartments__.keys())) > 1
2675                ):
2676                    assert (
2677                        self.__sDict__[sp]['compartment'] != None
2678                    ), '\nCOMPARTMENT ERROR: this model has multiple compartments defined {} but \"{}\" is not in one!'.format(
2679                        [
2680                            self.__compartments__[c]['name']
2681                            for c in list(self.__compartments__.keys())
2682                        ],
2683                        sp,
2684                    )
2685                # brett 2008 fudge this!
2686                if not self.__KeyWords__['Species_In_Conc'] and I_AM_FUDGING:
2687                    self.__sDict__[sp]['initial'] = (
2688                        self.__sDict__[sp]['initial']
2689                        / self.__settings__['compartment_fudge_factor']
2690                    )
2691                    setattr(self, sp, self.__sDict__[sp]['initial'])
2692                    setattr(self, '{}_init'.format(sp), self.__sDict__[sp]['initial'])
2693                    print(
2694                        'INFO: Rescaling species ({}) to size {}.'.format(
2695                            sp, self.__sDict__[sp]['initial']
2696                        )
2697                    )
2698
2699        self.__CsizeAllIdx__ = []
2700        self.__null_compartment__ = 1.0
2701        uses_null_compartments = False
2702        for s in self.__species__:
2703            if self.__HAS_COMPARTMENTS__:
2704                self.__CsizeAllIdx__.append(self.__sDict__[s]['compartment'])
2705            else:
2706                self.__CsizeAllIdx__.append('__null_compartment__')
2707                uses_null_compartments = True
2708        if uses_null_compartments:
2709            setattr(self, '__null_compartment___init', 1.0)
2710        ##  self.__CsizeFSIdx__ = []
2711        ##  for s in self.__fixed_species__:
2712        ##  if self.__HAS_COMPARTMENTS__:
2713        ##  self.__CsizeFSIdx__.append(self.__sDict__[s]['compartment'])
2714        ##  else:
2715        ##  self.__CsizeFSIdx__.append('__null_compartment__')
2716
2717        # Init compartment divisions vector - brett 2008
2718        ##  self.__CsizeAll__ = numpy.ones(len(self.__CsizeAllIdx__), 'd')
2719        if self.__HAS_COMPARTMENTS__:
2720            self.__CsizeAll__ = numpy.array(
2721                [self.__compartments__[c]['size'] for c in self.__CsizeAllIdx__], 'd'
2722            )
2723        else:
2724            self.__CsizeAll__ = numpy.ones(len(self.__CsizeAllIdx__), 'd')
2725        ##  self.__CsizeFS__ = numpy.array([self.__compartments__[c]['size'] for c in self.__CsizeFSIdx__], 'd')
2726
2727    # add new function types to model
2728    def InitialiseFunctions(self):
2729        for func in self.__functions__:
2730            F = Function(func, self)
2731            for arg in self.__functions__[func]['args']:
2732                F.setArg(arg.strip())
2733            F.addFormula(self.__functions__[func]['formula'])
2734            setattr(self, func, F)
2735        for func in self.__functions__:
2736            fobj = getattr(self, func)
2737            for f in fobj.functions:
2738                if f in self.__functions__:
2739                    setattr(fobj, f, getattr(self, f))
2740
2741    # add event types to model
2742    def InitialiseEvents(self):
2743        # return all event time points, even if not in self.sim_time
2744        self.__settings__['cvode_return_event_timepoints'] = True
2745        self.__events__ = []
2746        # for each event
2747        for e in self.__eDict__:
2748            ev = Event(e, self)
2749            ev._time_symbol = self.__eDict__[e]['tsymb']
2750            ev.setTrigger(self.__eDict__[e]['trigger'], self.__eDict__[e]['delay'])
2751            # for each assignment
2752            for ass in self.__eDict__[e]['assignments']:
2753                ev.setAssignment(ass, self.__eDict__[e]['assignments'][ass])
2754            self.__events__.append(ev)
2755            setattr(self, ev.name, ev)
2756        if len(self.__events__) > 0:
2757            self.__HAS_EVENTS__ = True
2758            print('Event(s) detected.')
2759        else:
2760            self.__HAS_EVENTS__ = False
2761
2762    def InitialiseModel(self):
2763        """
2764        InitialiseModel()
2765
2766        Initialise and set up dynamic model attributes and methods using the model defined in
2767        the associated PSC file
2768
2769        Arguments:
2770        None
2771
2772        """
2773        # TODO killkillkill
2774        self.__inspec__ = numpy.zeros((len(self.__species__)), 'd')
2775        self._D_s_Order, DvarUpString = self.__initvar__()  # Initialise s[] array
2776
2777        self.__CDvarUpString__ = compile(DvarUpString, 'DvarUpString', 'exec')
2778
2779        self.state_species = None
2780        self.state_flux = None
2781        ##  self.sim_res = None
2782        self.elas_var_u = None
2783        self.elas_var = None
2784        self.__vvec__ = numpy.zeros((self.__Nshape__[1]), 'd')
2785        self.__tvec_a__ = None
2786        self.__tvec_c__ = None
2787
2788        self._CVODE_Vtemp = numpy.zeros((self.__Nshape__[1]), 'd')
2789
2790        #  #debug
2791        #  self.display_debugcount = 0
2792        #  self.cntr = 50
2793
2794        par_map2store, par_remap = self.__initfixed__()  # Initialise fixed species
2795
2796        # self.__par_map2storeC =  par_map2store
2797        # self.__par_remapC =  par_remap
2798        self.__par_map2storeC = compile(par_map2store, 'par_map2store', 'exec')
2799        self.__par_remapC = compile(par_remap, 'par_remap', 'exec')
2800
2801        ##  self.__xMake = compile(xString,'xString','exec')
2802        ##  exec(self.__xMake)
2803
2804        # initialise rate equations
2805        mapString, mapString_R, vString = self.__initREQ__()
2806
2807        self.__mapFunc__ = compile(mapString, 'mapString', 'exec')
2808        self.__mapFunc_R__ = compile(mapString_R, 'mapString_R', 'exec')
2809        self.__vFunc__ = compile(vString, 'vString', 'exec')
2810        ##  exec(lambdaFuncsC)
2811
2812        # Machine specific values (for IEEE compliant FP machines this is around 2.e-16)
2813        # FutureNote: investigate arbitrary precision FP in Python, probably GNU-GMP based - brett 20040326
2814        self.__settings__['mach_floateps'] = mach_spec.eps
2815
2816        # PySCeS mode switches
2817        # this affects number output formatting in PySCeS)
2818        self.__settings__['mode_number_format'] = '%2.4e'
2819        # 0:initval, 1:zeroval, 2:lastss 3:sim_res[-1]
2820        self.__settings__['mode_sim_init'] = 0
2821        # maximum number of auto-stepsize adjustments
2822        self.__settings__['mode_sim_max_iter'] = 3
2823
2824        # TODO UPGRADE
2825        self.mode_state_init = 0  # 0:initval, 1:zeroval, 2:sim, 3:%state
2826        self.mode_solver = 'HYBRD'  # 0:HYBRD, 1:FINTSLV, 2:NLEQ2
2827        self.mode_solver_fallback = 1  # 0:Solver failure fails,
2828        # 1:solver failure falls back to NLEQ2 if present then FINTSLV (see next)
2829        self.STATE_extra_output = []  # extra data added to data_sstate object
2830        # factor to multiply the previous ss used as initialiser
2831        self.__settings__['mode_state_init3_factor'] = 0.1
2832        # the simulation array used to initialise the steady state
2833        self.__mode_state_init2_array__ = numpy.logspace(0, 5, 18)
2834        self.__settings__['mode_state_mesg'] = 1  # happy exit message from State()
2835        # give last best solutions or NaN as solution
2836        self.__settings__['mode_state_nan_on_fail'] = False
2837        # the irritating switching to message
2838        self.__settings__['solver_switch_warning'] = True
2839        # 0:no fallback to forward integration 1:fallback to integration
2840        self.__settings__['mode_solver_fallback_integration'] = 1
2841        # this is now initialised in the model parsing sectiomn ParseModel
2842        # the order of ScipyDerivative - 3 seems good for most situations
2843        self.__settings__['mode_elas_deriv_order'] = 3
2844        # MCA scaling option 0:unscaled E+C+Eig 1:scaled E+C+Eig
2845        self.__settings__['mode_mca_scaled'] = 1
2846        # 0:normal, 1:extended eigen value + left/right vectors
2847        self.__settings__['mode_eigen_output'] = 0
2848        # under consideration
2849        # 0:warnings displayed, 1:warnings suppressed
2850        self.__settings__['mode_suppress_info'] = 0
2851        # new compartment stuff (using dictionary directly) - brett 2008
2852
2853        ##  self.Species_In_Conc = False
2854
2855        # misc settings
2856        # write an id header before the array
2857        self.__settings__['write_array_header'] = 1
2858        # write a empty line before the array
2859        self.__settings__['write_array_spacer'] = 1
2860        self.__settings__['write_array_html_header'] = 1  # write html page header
2861        self.__settings__['write_array_html_footer'] = 1  # write html page footer
2862        self.__settings__['write_array_html_format'] = '%2.4f'  # html number format
2863        # lines to write before flushing to disk
2864        self.__settings__['write_arr_lflush'] = 5
2865
2866        # Hybrd options (zero value means routine decides)
2867        self.__settings__['hybrd_xtol'] = 1.0e-12  # relative error tolerance
2868        # Maximum number of calls, zero means then 100*(len(species)+1)
2869        self.__settings__['hybrd_maxfev'] = 0
2870        # A suitable step length for the forward-difference approximation of the Jacobian
2871        self.__settings__['hybrd_epsfcn'] = copy.copy(
2872            self.__settings__['mach_floateps']
2873        )
2874        # A parameter determining the initial step bound in interval (0.1,100)
2875        self.__settings__['hybrd_factor'] = 100
2876        self.__settings__['hybrd_mesg'] = 1  # print the exit status message
2877
2878        # Finstslv options (forward integration solver) - brett (20030331)
2879        # max allowed deviation between max(sim_res) to be a steady state
2880        self.__settings__['fintslv_tol'] = 1.0e-3
2881        # threshold number of steps where deviation < atol to be declared a steady state
2882        self.__settings__['fintslv_step'] = 5
2883        # a "saturation" type range - should be ok for most systems
2884        self.__fintslv_range__ = numpy.array(
2885            [
2886                1,
2887                10,
2888                100,
2889                1000,
2890                5000,
2891                10000,
2892                50000,
2893                50100,
2894                50200,
2895                50300,
2896                50400,
2897                50500,
2898                50600,
2899                50700,
2900                50800,
2901                50850,
2902                50900,
2903                50950,
2904                51000,
2905            ],
2906            'd',
2907        )
2908        # self.__fintslv_range__ = numpy.array([1,10,100,1000,5000,10000,50000,100000,500000,1000000,1000100, 1000200,1000300,1000400,1000500,1000600,1000700,1000800],'d')
2909        self.__settings__['fintslv_rmult'] = 1.0
2910
2911        # NLEQ2 options (optional non-linear solver) - brett (20030331)
2912        # IOPT
2913        # use advanced NLEQ2 features ... may speedup some parameter scans
2914        self.__settings__['nleq2_advanced_mode'] = False
2915        # nitmax growth factor per nleq2 iteration
2916        self.__settings__['nleq2_growth_factor'] = 10
2917        # number of itertions to loop the solver through (2 should almost always be sufficient)
2918        self.__settings__['nleq2_iter'] = 3
2919        # maximum numbe rof iterations in advanced mode
2920        self.__settings__['nleq2_iter_max'] = 10
2921
2922        self.__settings__['nleq2_rtol'] = 1.0e-8  # automatically calculated by nleq12
2923        self.__settings__['nleq2_jacgen'] = 2  # 2:numdiff, 3:numdiff+feedback
2924        # 0:xscal lower thresholdof scaling vector, 1:always scaling vector
2925        self.__settings__['nleq2_iscaln'] = 0
2926        # Dangerous anything not zero seqfaults
2927        ## self.__settings__['nleq2_mprerr'] = 1       # 0:no output, 1:error, 2:+warning, 3:+info
2928        # 1:linear, 2:mildly non-lin, 3:highly non-lin, 4:extremely non-lin
2929        self.__settings__['nleq2_nonlin'] = 4
2930        # 0:no Broyden approx. rank-1 updates, 1:Broyden approx. rank-1 updates
2931        self.__settings__['nleq2_qrank1'] = 1
2932        # 0:Automatic row scaling is active, 1:inactive
2933        self.__settings__['nleq2_qnscal'] = 0
2934        # 0:auto damping strategy, 1:damping on, 2:damping off
2935        self.__settings__['nleq2_ibdamp'] = 0
2936        # on non-superlinear convergance nitmax *= this (ierr=5)
2937        self.__settings__['nleq2_nitmax_growth_factor'] = 4
2938        # 0:default(2) 1:convergance not checked, 2:+'weak stop', 3:+'hard stop' criterion
2939        self.__settings__['nleq2_iormon'] = 2
2940        self.__settings__['nleq2_mesg'] = 1  # print the exit status message
2941        # TODO:
2942        self.nleq2_nitmax = 50  # optimized and self adjusting :-)
2943
2944        # Other SteadyState options
2945        # the initial values of 1:zeroval smaller than 1.0e-6 sometimes give problems
2946        self.__settings__['small_concentration'] = 1.0e-6
2947        ##  self.__state_set_conserve__ = 1
2948        self.__StateOK__ = True
2949
2950        self.data_sstate = None
2951        self._sim = None  # new object for recarray with simulation results
2952        self.data_sim = None  # the new integration results data object
2953        self.data_stochsim = None  # the new stochastic integration results data object
2954        self.__scan2d_results__ = None  # yaaaay gnuplot is back
2955        self.__scan2d_pars__ = None
2956
2957        # Simulate/lsoda options (zero value means routine decides)
2958        # The input parameters rtol and atol determine the error
2959        self.__settings__['lsoda_atol'] = 1.0e-12
2960        # control performed by the solver. -- johann 20050217 changed from 1e-5
2961        self.__settings__['lsoda_rtol'] = 1.0e-7
2962        # maximum number (internally defined) steps allowed per point. 0: x <= 500
2963        self.__settings__["lsoda_mxstep"] = 0
2964        # the step size to be attempted on the first step.
2965        self.__settings__["lsoda_h0"] = 0.0
2966        self.__settings__["lsoda_hmax"] = 0.0  # the maximum absolute step size allowed.
2967        self.__settings__["lsoda_hmin"] = 0.0  # the minimum absolute step size allowed.
2968        # maximum order to be allowed for the nonstiff (Adams) method.
2969        self.__settings__["lsoda_mxordn"] = 12
2970        # maximum order to be allowed for the stiff (BDF) method.
2971        self.__settings__["lsoda_mxords"] = 5
2972        self.__settings__["lsoda_mesg"] = 1  # print the exit status message
2973
2974        # try to select the best integration algorithm
2975        self.mode_integrator = 'LSODA'  # LSODA/CVODE set the intgration algorithm
2976        if self.__HAS_EVENTS__:
2977            if _HAVE_ASSIMULO:
2978                print(
2979                    '\nINFO: events detected and we have Assimulo installed,\n\
2980switching to CVODE (mod.mode_integrator=\'CVODE\').'
2981                )
2982                self.mode_integrator = 'CVODE'
2983            else:
2984                print(
2985                    '\nWARNING: PySCeS needs Assimulo installed for event handling,\n\
2986PySCeS will continue with LSODA (NOTE: ALL EVENTS WILL BE IGNORED!).'
2987                )
2988                print(
2989                    'Assimulo may be installed from conda-forge or compiled from source.\n\
2990See: https://jmodelica.org/assimulo'
2991                )
2992                self.__events__ = []
2993        # if self.__HAS_RATE_RULES__ or self.__HAS_COMPARTMENTS__:
2994        if self.__HAS_RATE_RULES__:
2995            if _HAVE_ASSIMULO:
2996                print(
2997                    'INFO: RateRules detected and Assimulo installed,\n\
2998switching to CVODE (mod.mode_integrator=\'CVODE\').\n'
2999                )
3000                self.mode_integrator = 'CVODE'
3001            else:
3002                print(
3003                    '\nWARNING: RateRules detected! PySCeS prefers CVODE but will continue with LSODA\n\
3004(NOTE: VARIABLE COMPARTMENTS ARE NOT SUPPORTED WITH LSODA!)'
3005                )
3006                print(
3007                    'Assimulo may be installed from conda-forge or compiled from source.\n\
3008See: https://jmodelica.org/assimulo'
3009                )
3010
3011        # CVode options
3012        self.mode_integrate_all_odes = False  # only available with CVODE
3013        self.__settings__["cvode_abstol"] = 1.0e-15  # absolute tolerance
3014        # self.__settings__["cvode_abstol_max"]  = 1.0e-3  # not used anymore
3015        # self.__settings__["cvode_abstol_factor"] = 1.0e-6  # not used in Assimulo
3016        self.__settings__["cvode_reltol"] = 1.0e-9  # relative tolerance
3017        # self.__settings__["cvode_auto_tol_adjust"] = True  # not used in Assimulo
3018        self.__settings__["cvode_mxstep"] = 1000  # max step default
3019        # print some pretty stuff after a simulation
3020        self.__settings__["cvode_stats"] = False
3021
3022        if self.__HAS_PIECEWISE__ and self.__settings__["cvode_reltol"] <= 1.0e-9:
3023            self.__settings__["cvode_reltol"] = 1.0e-6
3024            print(
3025                'INFO: Piecewise functions detected increasing CVODE tolerance slightly\n\
3026(mod.__settings__[\"cvode_reltol\"] = 1.0e-9 ).'
3027            )
3028
3029        # Normal simulation options
3030        self.sim_start = 0.0
3031        self.sim_end = 10.0
3032        self.sim_points = 20.0
3033        sim_steps = (self.sim_end - self.sim_start) / self.sim_points
3034        self.sim_time = numpy.arange(
3035            self.sim_start, self.sim_end + sim_steps, sim_steps
3036        )
3037        self.CVODE_extra_output = []
3038        self.CVODE_xdata = None
3039        self.__SIMPLOT_OUT__ = []
3040
3041        # elasticity options
3042        self.__settings__["elas_evar_upsymb"] = 1  # attach elasticities
3043        self.__settings__["elas_epar_upsymb"] = 1  # attach parameter elasticities
3044        # remap steady state values ... no if SimElas
3045        self.__settings__["elas_evar_remap"] = 1
3046        # used to determine a stepsize dx=So*factor
3047        self.__settings__['mode_elas_deriv_factor'] = 0.0001
3048        # minimum value dx is allowed to have
3049        self.__settings__['mode_elas_deriv_min'] = 1.0e-12
3050        # replaces zero fluxes with a very small number
3051        self.__settings__['elas_zero_flux_fix'] = False
3052        # replaces zero concentrations with a very small number
3053        self.__settings__['elas_zero_conc_fix'] = False
3054        # if Infinite values are created when scaling set to zero
3055        self.__settings__['elas_scaling_div0_fix'] = False
3056
3057        # MCA options
3058        self.__settings__["mca_ccj_upsymb"] = 1  # attach the flux control coefficients
3059        # attach the concentration control coefficients
3060        self.__settings__["mca_ccs_upsymb"] = 1
3061        self.__settings__["mca_ccall_fluxout"] = 1  # in .showCC() output flux cc's
3062        self.__settings__["mca_ccall_concout"] = 1  # in .showCC() output conc cc's
3063        # in .showCC() all CC's group by reaction
3064        self.__settings__["mca_ccall_altout"] = 0
3065
3066        # gone in 60 seconds brett2008 (Refactored to PyscesLink)
3067        ##  # Elementary mode options
3068        ##  self.emode_userout = 0     # write metatool output to file 0:no, 1:yes
3069        ##  self.emode_intmode = 0  # 0:float metatool, 1:integer metatool
3070        ##  self.emode_file = self.ModelFile[:-4] + '_emodes'
3071
3072        # pitcon (pitcon 6.1) continuation options - brett 20040429
3073        # my interface controls
3074        # in the REq evaluation values <1.e-15 = 1.e-15
3075        self.__settings__["pitcon_fix_small"] = 0
3076        # parameter space that pitcon must search in
3077        self.pitcon_par_space = numpy.logspace(-1, 3, 10)
3078        # number of iterations to search for every point in par_space
3079        self.pitcon_iter = 10
3080        # initialize with non steady-state values 0:no,1:yes
3081        self.__settings__["pitcon_allow_badstate"] = 0
3082        # generate fluxes as output
3083        self.__settings__["pitcon_flux_gen"] = 1
3084        # drop pitcon results containing negative concentrations 0:no,1:yes
3085        self.__settings__["pitcon_filter_neg"] = 1
3086        # drop output results containing negative concentrations 0:no,1:yes
3087        self.__settings__["pitcon_filter_neg_res"] = 0
3088        self.__settings__["pitcon_max_step"] = 30.0  # Maximum stepsize
3089        # TODO:
3090        self.pitcon_target_points = []  # list of calculated target points
3091        self.pitcon_limit_points = []  # list of calculated limit points
3092        self.pitcon_targ_val = 0.0  # Target value (Seek solution with iwork[4]=)
3093
3094        # moved to InitialiseConservationArrays
3095        # self.__settings__["pitcon_max_step"] = 10*(len(self.__SI__)+1) #max corrector steps
3096
3097        # pitcon integer options iwork in pitcon/dpcon61.f
3098        self.__settings__["pitcon_init_par"] = 1  # Use X(1) for initial parameter
3099        # Parameterization option 0:allows program
3100        self.__settings__["pitcon_par_opt"] = 0
3101        self.__settings__["pitcon_jac_upd"] = 0  # Update jacobian every newton step
3102        self.__settings__["pitcon_targ_val_idx"] = 0  # Seek target values for X(n)
3103        self.__settings__["pitcon_limit_point_idx"] = 0  # Seek limit points in X(n)
3104        self.__settings__["pitcon_output_lvl"] = 0  # Control amount of output.
3105        # Jacobian choice. 0:supply jacobian,1:use forward difference,2:central difference
3106        self.__settings__["pitcon_jac_opt"] = 1
3107        # pitcon float options rwork in pitcon/dpcon61.f
3108        self.__settings__["pitcon_abs_tol"] = 0.00001  # Absolute error tolerance
3109        self.__settings__["pitcon_rel_tol"] = 0.00001  # Relative error tolerance
3110        self.__settings__["pitcon_min_step"] = 0.01  # Minimum stepsize
3111        self.__settings__["pitcon_start_step"] = 0.3  # Starting stepsize
3112        self.__settings__["pitcon_start_dir"] = 1.0  # Starting direction +1.0/-1.0
3113        self.__settings__["pitcon_max_grow"] = 3.0  # maximum growth factor
3114
3115        # setup conservation matrices (L_switch is set by stoich to 1 if it "detects" conservation)
3116        self.InitialiseConservationArrays()
3117
3118        # scan option - removed from initsimscan
3119        self.scan_in = ''
3120        self.scan_out = []
3121        self._scan = None
3122        self.scan_res = None
3123        # should scan1 run mca analysis 0:no,1:elas,2:cc
3124        self.__settings__["scan1_mca_mode"] = 0
3125        # should scan1 drop invalid steady states (rare)?
3126        self.__settings__["scan1_dropbad"] = 0
3127        # invalid steady states are returned as NaN
3128        self.__settings__["scan1_nan_on_bad"] = True
3129        # print out progress messages for large (>20) scans
3130        self.__settings__["scan1_mesg"] = True
3131        self.__scan_errors_par__ = None  # collect errors, parameters values
3132        self.__scan_errors_idx__ = None  # collect errors, indexes
3133
3134    def InitialiseConservationArrays(self):
3135        """
3136        Initialise conservation related vectors/array was in InitialiseModel but has been
3137        moved out so is can be called by when the stoichiometry is reanalysed
3138
3139        """
3140        # Init and Sx vectors # these vectors are multiplying all by themselves - brett
3141        self.__SI__ = numpy.zeros((self.__lzeromatrix__.shape[1]), 'd')
3142        self.__SALL__ = numpy.zeros((len(self.__species__)), 'd')
3143        self.__settings__["pitcon_max_step"] = 10 * (
3144            len(self.__SI__) + 1
3145        )  # max corrector steps
3146        if self.__HAS_MOIETY_CONSERVATION__ == True:
3147            # reorder the conservation matrix so that it is compatible with L
3148            idx = [
3149                list(self.conservation_matrix_col).index(n)
3150                for n in range(len(self.conservation_matrix_col))
3151            ]
3152            self.__reordered_lcons = self.conservation_matrix.take(idx, 1)
3153            if self.__HAS_COMPARTMENTS__ and self.__KeyWords__['Species_In_Conc']:
3154                self.__Build_Tvec__(amounts=False)
3155            else:
3156                self.__Build_Tvec__(amounts=True)
3157            self.showConserved(screenwrite=0)
3158
3159    def InitialiseOldFunctions(self):
3160        """
3161        InitialiseOldFunctions()
3162
3163        Parse and initialise user defined functions specified by !T !U in the PSC input file
3164
3165        Arguments:
3166        None
3167
3168        """
3169        self.__CODE_user = compile(self._Function_user, '_Function_user', 'exec')
3170        try:
3171            exec(self.__CODE_user)
3172            # print 'done.'
3173        except Exception as e:
3174            print('WARNING: User function error\n', e)
3175            print('This might be due to non-instantiated (e.g. MCA/state) attributes')
3176            print('Make sure the attributes that are used in your function exist ...')
3177            print('and manually load using the self.ReloadUserFunc() method')
3178
3179        # print '\nInitializing init function ...'
3180        # print self._Function_init
3181
3182        try:
3183            self.ReloadInitFunc()
3184        except Exception as e:
3185            print('WARNING: Init function error\n', e)
3186            print(
3187                'This function is meant to be used exclusively for the initialization'
3188            )
3189            print(
3190                'of PySCeS properties and expressions based on model attributes defined in the input file.'
3191            )
3192            print('Reinitialize with ReloadInitFunc()')
3193
3194    def ReloadUserFunc(self):
3195        """
3196        ReloadUserFunc()
3197
3198        Recompile and execute the user function (!U) from the input file.
3199
3200        Arguments:
3201        None
3202
3203        """
3204        ##  print "User functions disabled ..."
3205        self.__CODE_user = compile(self._Function_user, '_Function_user', 'exec')
3206        try:
3207            exec(self.__CODE_user)
3208        except Exception as e:
3209            print('WARNING: User function load error\n', e)
3210
3211    def ReloadInitFunc(self):
3212        """
3213        ReloadInitFunc()
3214
3215        Recompile and execute the user initialisations (!I) as defined in the PSC input file.
3216        and in mod.__InitFuncs__.
3217
3218        UPDATE 2015: can now be used to define InitialAssignments (no need for self.* prefix in input file)
3219
3220        Arguments:
3221        None
3222
3223        """
3224
3225        def topolgical_sort(graph_unsorted):
3226            """
3227            Repeatedly go through all of the nodes in the graph, moving each of
3228            the nodes that has all its edges resolved, onto a sequence that
3229            forms our sorted graph. A node has all of its edges resolved and
3230            can be moved once all the nodes its edges point to, have been moved
3231            from the unsorted graph onto the sorted one.
3232            """
3233
3234            # This is the list we'll return, that stores each node/edges pair
3235            # in topological order.
3236            graph_sorted = []
3237
3238            # Convert the unsorted graph into a hash table. This gives us
3239            # constant-time lookup for checking if edges are unresolved, and
3240            # for removing nodes from the unsorted graph.
3241            graph_unsorted = dict(graph_unsorted)
3242
3243            # Run until the unsorted graph is empty.
3244            while graph_unsorted:
3245
3246                # Go through each of the node/edges pairs in the unsorted
3247                # graph. If a set of edges doesn't contain any nodes that
3248                # haven't been resolved, that is, that are still in the
3249                # unsorted graph, remove the pair from the unsorted graph,
3250                # and append it to the sorted graph. Note here that by using
3251                # using the items() method for iterating, a copy of the
3252                # unsorted graph is used, allowing us to modify the unsorted
3253                # graph as we move through it. We also keep a flag for
3254                # checking that that graph is acyclic, which is true if any
3255                # nodes are resolved during each pass through the graph. If
3256                # not, we need to bail out as the graph therefore can't be
3257                # sorted.
3258                acyclic = False
3259                for node, edges in list(graph_unsorted.items()):
3260                    for edge in edges:
3261                        if edge in graph_unsorted:
3262                            break
3263                    else:
3264                        acyclic = True
3265                        del graph_unsorted[node]
3266                        graph_sorted.append((node, edges))
3267
3268                if not acyclic:
3269                    # Uh oh, we've passed through all the unsorted nodes and
3270                    # weren't able to resolve any of them, which means there
3271                    # are nodes with cyclic edges that will never be resolved,
3272                    # so we bail out with an error.
3273                    raise RuntimeError("A cyclic dependency occurred")
3274
3275            return graph_sorted
3276
3277        simpleAss = []
3278        exprsAss = []
3279        symbolsX = []
3280        for init in self.__InitFuncs__:
3281            if hasattr(self, init):
3282                # TODO: to be continued by brett
3283                # print(init, self.__InitFuncs__[init])
3284                if type(self.__InitFuncs__[init]) == str:
3285                    if self.__InitFuncs__[init].isdigit():
3286                        # self.__InitFuncs__[init] = eval(self.__InitFuncs__[init])
3287                        # setattr(self, init, self.__InitFuncs__[init])
3288                        # self.__InitFuncs__[init] = compile(self.__InitFuncs__[init], '_InitFunc_', 'single')
3289                        simpleAss.append((init, eval(self.__InitFuncs__[init])))
3290                        # setattr(self, init, eval(self.__InitFuncs__[init]))
3291                    else:
3292                        InfixParser.setNameStr('self.', '')
3293                        # InfixParser.SymbolReplacements = {'_TIME_':'mod._TIME_'}
3294                        InfixParser.parse(str(self.__InitFuncs__[init]))
3295                        piecewises = InfixParser.piecewises
3296                        symbols = InfixParser.names
3297                        for s_ in symbols:
3298                            if s_ not in symbolsX:
3299                                symbolsX.append(s_)
3300                        if init not in symbolsX:
3301                            symbolsX.append(init)
3302                        functions = InfixParser.functions
3303                        code_string = 'self.{} = {}'.format(init, InfixParser.output)
3304                        xcode = compile(code_string, '_InitAss_', 'exec')
3305                        exprsAss.append((init, xcode, symbols))
3306                        # setattr(self, init, eval(xcode))
3307                    # else:
3308                    ##setattr(self, init, self.__InitFuncs__[init])
3309                    # self.__InitFuncs__[init] = compile(self.__InitFuncs__[init], '_InitFunc_', 'single')
3310                    # setattr(self, init, eval(self.__InitFuncs__[init]))
3311                else:
3312                    setattr(self, init, self.__InitFuncs__[init])
3313            else:
3314                assert hasattr(self, init), '\nModelInit error'
3315        # TODO: to be continued by brett
3316        # print('symbolsX', symbolsX)
3317        for init, val in simpleAss:
3318            # print('s', init,val)
3319            setattr(self, init, val)
3320        execOrder = []
3321        for init, xcode, symbols2 in exprsAss:
3322            symbols2 = [symbolsX.index(s_) for s_ in symbols2]
3323            execOrder.append((symbolsX.index(init), symbols2))
3324            # TODO: to be continued by brett
3325            # print('x', symbols2)
3326            # print('e', init,code_string)
3327            eval(xcode)
3328        # TODO: to be continued by brett
3329        # print(execOrder)
3330        # print(topolgical_sort(execOrder))
3331        # print(symbolsX)
3332
3333    def __initREQ__(self):
3334        """
3335        __initREQ__()
3336
3337        Compile and initialise the model rate equations and associated arrays. Rate equations are
3338        generated as lambda functions callable as mod.R1(mod)
3339
3340        Arguments:
3341        None
3342
3343        """
3344        # create work arrays for Reactions and variable species
3345        # s = numpy.zeros(len(self.__species__),'d')
3346        # self.Vtemp = numpy.zeros(len(self.__reactions__),'d')
3347        # This appears to be redundant leftover code - brett 20031215
3348
3349        if self.__settings__['display_debug'] == 1:
3350            pass
3351            # print 'Vtemp'
3352
3353        # create the map string by taking the VarReagent[] and assigning it to an s[index]
3354        # a reverse map function mapping self.sXi back to self.init[x] for simulation initiation
3355        mapString = ''
3356        mapString_R = ''
3357        for x in range(0, len(self.__species__)):
3358            mapString += 'self.{} = s[{}]\n'.format(self.__species__[x], x)
3359            mapString_R += 'self.__inspec__[{}] = self.{}_init\n'.format(
3360                x, self.__species__[x],
3361            )
3362        ##  print mapString_R
3363        # this creates the mapping string for the derivative functions
3364
3365        if self.__settings__['display_debug'] == 1:
3366            print('mapString')
3367            print(mapString)
3368            print('mapString_R')
3369            print(mapString_R)
3370
3371        # create the REq string in ReactionIDs order as a single multiline definition
3372        # using indexes: vString (Vtemp[x] =)
3373        # or a list of individual compile definitions: vArray (v + ReactionID = )
3374        vString = ''
3375        ##  vString2 = ''
3376        ##  DvOrder = []
3377        EStat = []
3378        ##  dispREq = ''
3379
3380        symbR = {}
3381        if (
3382            len(list(self.__rules__.keys())) > 0
3383            and self.__settings__['mode_substitute_assignment_rules']
3384        ):
3385            # create substitution dictionary
3386            for ass in self.__rules__:
3387                symbR.update(
3388                    {self.__rules__[ass]['name']: self.__rules__[ass]['code_string']}
3389                )
3390
3391        for x in range(0, len(self.__reactions__)):
3392            req1 = self.__nDict__[self.__reactions__[x]]['RateEq']
3393            ##  DvOrder.append(self.__reactions__[x])
3394            vString += 'Vtemp[{}] = self.{}()\n'.format(x, self.__reactions__[x])
3395
3396            # Core update inspired by Core2, lambda functions replaced by Reaction instances
3397
3398            if (
3399                len(list(self.__rules__.keys())) > 0
3400                and self.__settings__['mode_substitute_assignment_rules']
3401            ):
3402                # substitute assignment rules
3403                InfixParser.setNameStr('self.', '')
3404                InfixParser.FunctionReplacements = symbR
3405                InfixParser.parse(req1.replace('self.', ''))
3406                req2 = InfixParser.output
3407                ##  req1_names = InfixParser.names
3408                rObj = ReactionObj(self, self.__reactions__[x], req2, 'self.')
3409            else:
3410                rObj = ReactionObj(self, self.__reactions__[x], req1, 'self.')
3411            self.__ParsePiecewiseFunctions__(rObj.piecewises)
3412            rObj.compartment = self.__nDict__[rObj.name]['compartment']
3413
3414            setattr(self, self.__reactions__[x], rObj)
3415
3416        # this is to check that if a model defines compartments then REq's MUST be in amounts brett2008
3417        # brett2008
3418        if self.__HAS_COMPARTMENTS__:
3419            cnames = [self.__compartments__[c]['name'] for c in self.__compartments__]
3420            warnings = ''
3421            for rr in self.__reactions__:
3422                rrobj = getattr(self, rr)
3423                warn = False
3424                if (
3425                    rrobj.compartment == None
3426                    and self.__settings__['display_compartment_warnings']
3427                ):
3428                    warnings += "# {} is not located in a compartment.\n".format(
3429                        rrobj.name
3430                    )
3431                else:
3432                    for comp in cnames:
3433                        if comp in rrobj.symbols:
3434                            warn = False
3435                            break
3436                        else:
3437                            warn = True
3438                    if warn:
3439                        warnings += "# {}: {}\n#  assuming kinetic constants are flow constants.\n".format(
3440                            rr, rrobj.formula
3441                        )
3442            if warnings != '' and self.__settings__['display_compartment_warnings']:
3443                print('\n# -- COMPARTMENT WARNINGS --')
3444                print(warnings)
3445        if self.__settings__['display_debug'] == 1:
3446            print('vString')
3447            print(vString)
3448            ##  print 'vString2'
3449            ##  print vString2
3450            ##  print 'DvOrder'
3451            ##  print DvOrder
3452        return mapString, mapString_R, vString
3453
3454    def __initmodel__(self):
3455        """
3456        __initmodel__()
3457
3458        Generate the stoichiometric matrix N from the parsed model description.
3459        Returns a stoichiometric matrix (N)
3460
3461        Arguments:
3462        None
3463
3464        """
3465        VarReagents = ['self.' + s for s in self.__species__]
3466        StoicMatrix = numpy.zeros((len(VarReagents), len(self.__reactions__)), 'd')
3467        for reag in VarReagents:
3468            for id in self.__reactions__:
3469                if reag in list(self.__nDict__[id]['Reagents'].keys()):
3470                    StoicMatrix[VarReagents.index(reag)][
3471                        self.__reactions__.index(id)
3472                    ] = self.__nDict__[id]['Reagents'][reag]
3473        return StoicMatrix
3474
3475    def __initvar__(self):
3476        """
3477        __initvar__()
3478
3479        Compile and initialise the model variable species and derivatives and associated mapping arrays.
3480
3481        Arguments:
3482        None
3483
3484        """
3485        mvarString = ''
3486        sOrder = []
3487        self.__remaps = ''
3488        DvarUpString = ''
3489
3490        # self.__inspec__ = numpy.zeros((len(self.__species__)),'d') moved to ParseModel
3491        for x in range(0, len(self.__species__)):
3492            key = self.__species__[x]
3493            ##  self.__inspec__[x] = eval(key)
3494            self.__inspec__[x] = getattr(self, key)
3495            mvarString += 'self.__inspec__[{}] = float(self.{})\n'.format(x, key)
3496            self.__remaps += 'self.{} = self.{}_ss\n'.format(key, key)
3497            sOrder.append('self.' + key)
3498            DvarUpString += 'self.{} = input[{}]\n'.format(self.__species__[x], x)
3499
3500        if self.__settings__['display_debug'] == 1:
3501            print('s_initDeriv')
3502            print(s_initDeriv)
3503            print('\n__remaps')
3504            print(self.__remaps)
3505            print('\nDvarUpString')
3506            print(DvarUpString)
3507
3508        mvarFunc = compile(mvarString, 'mvarString', 'exec')
3509        return sOrder, DvarUpString
3510
3511    def __initfixed__(self):
3512        """
3513        __initfixed__()
3514
3515        Compile and initialise the fixed species and associated mapping arrays
3516
3517        Arguments:
3518        None
3519
3520        """
3521        runmapString = ''
3522        if self.__settings__['display_debug'] == 1:
3523            print('InitParams2')
3524            print(self.__parameters__)
3525            print('InitStrings2')
3526            ##  print self.__InitStrings
3527
3528        # Initialise parameter elasticities
3529        par_map2store = ''
3530        par_remap = ''
3531        for x in range(len(self.__parameters__)):
3532            par_map2store += 'parVal_hold[{}] = self.{}\n'.format(
3533                x, self.__parameters__[x]
3534            )
3535            par_remap += 'self.{} = parVal_hold[{}]\n'.format(self.__parameters__[x], x)
3536
3537        if self.__settings__['display_debug'] == 1:
3538            print('par_map2store')
3539            print(par_map2store)
3540            print('par_remap')
3541            print(par_remap)
3542        return (par_map2store, par_remap)
3543
3544    # pysces core - the steady-state solver and integration routines
3545    def _SpeciesAmountToConc(self, s):
3546        """takes and returns s"""
3547        for x in range(len(self.__CsizeAllIdx__)):
3548            self.__CsizeAll__[x] = getattr(self, self.__CsizeAllIdx__[x])
3549        ##  print '__CALL__', self.__CsizeAll__
3550        return s / self.__CsizeAll__
3551
3552    def _SpeciesConcToAmount(self, s):
3553        """takes and returns s"""
3554        for x in range(len(self.__CsizeAllIdx__)):
3555            self.__CsizeAll__[x] = getattr(self, self.__CsizeAllIdx__[x])
3556        ##  print '__CALL__', self.__CsizeAll__
3557        return s * self.__CsizeAll__
3558
3559    def _FixedSpeciesAmountToConc(self):
3560        for x in range(len(self.__fixed_species__)):
3561            am = getattr(self, self.__fixed_species__[x])
3562            self.__CsizeFS__[x] = getattr(self, self.__CsizeFSIdx__[x])
3563            setattr(self, self.__fixed_species__[x], am / self.__CsizeFS__[x])
3564
3565    def _FixedSpeciesConcToAmount(self):
3566        for x in range(len(self.__fixed_species__)):
3567            cn = getattr(self, self.__fixed_species__[x])
3568            self.__CsizeFS__[x] = getattr(self, self.__CsizeFSIdx__[x])
3569            setattr(self, self.__fixed_species__[x], cn * self.__CsizeFS__[x])
3570
3571    # extract SI and "correct" SD in s solve for v
3572    # Vtemp is passed through the function to avoid an irritating bug
3573    # that appears if it isn't (ie defined as a global) - brett
3574    def _EvalREq2_alt(self, s, Vtemp):
3575        """
3576        _EvalREq2_alt(s,Vtemp)
3577
3578        Evaluate the rate equations correcting for mass conservation.
3579        Takes full [s] returns full [s] --> moiety aware
3580
3581        Arguments:
3582
3583        s: a vector of species cosnservations
3584        Vtemp: the rate vector
3585
3586        """
3587        # form an SI vector
3588        for x in range(0, len(self.lzeromatrix_col)):
3589            self.__SI__[x] = s[self.lzeromatrix_col[x]]
3590
3591        # replace SD with SI calculated values
3592        for x in range(0, len(self.lzeromatrix_row)):
3593            s[self.lzeromatrix_row[x]] = self.__tvec_a__[x] + numpy.add.reduce(
3594                self.__lzeromatrix__[x, :] * self.__SI__
3595            )
3596        return self._EvalREq(s, Vtemp)
3597
3598    def _EvalREq(self, s, Vtemp):
3599        if self.__HAS_RATE_RULES__:
3600            exec(self.__CODE_raterule_map)
3601        if self.__HAS_COMPARTMENTS__ and self.__KeyWords__['Species_In_Conc']:
3602            s = self._SpeciesAmountToConc(s)
3603        exec(self.__mapFunc__)
3604        try:
3605            self.Forcing_Function()
3606        except Exception as de:
3607            print('INFO: forcing function failure', de)
3608        try:
3609            exec(self.__vFunc__)
3610        except (
3611            ArithmeticError,
3612            AttributeError,
3613            NameError,
3614            ZeroDivisionError,
3615            ValueError,
3616        ) as detail:
3617            print('INFO: REq evaluation failure:', detail)
3618            Vtemp[:] = self.__settings__['mach_floateps']
3619        if self.__HAS_RATE_RULES__:
3620            try:
3621                exec(self.__CODE_raterule)
3622            except (
3623                ArithmeticError,
3624                AttributeError,
3625                NameError,
3626                ZeroDivisionError,
3627                ValueError,
3628            ) as detail:
3629                print('INFO: RateRule evaluation failure:', detail)
3630        return Vtemp
3631
3632    def _EvalREq2(self, s, Vtemp):
3633        """
3634        _EvalREq2(s,Vtemp)
3635
3636        Evaluate the rate equations, as PySCeS uses the reduced set of ODE's for its core operations, this method
3637        takes mass conservation into account and regenerates the full species vector
3638        takes [si] returns full [s]
3639
3640        Arguments:
3641
3642        s: species vector
3643        Vtemp: rate vector
3644
3645        """
3646        # stick SI into s using Nrrow order
3647        for x in range(len(s)):
3648            self.__SALL__[self.nrmatrix_row[x]] = s[x]
3649        # stick SD into s using lzerorow order
3650        for x in range(len(self.lzeromatrix_row)):
3651            self.__SALL__[self.lzeromatrix_row[x]] = self.__tvec_a__[
3652                x
3653            ] + numpy.add.reduce(self.__lzeromatrix__[x, :] * s)
3654        return self._EvalREq(self.__SALL__, Vtemp)
3655
3656    def _EvalODE(self, s, Vtemp):
3657        """
3658        _EvalODE(s,Vtemp)
3659
3660        Core ODE evaluation routine evaluating the reduced set of ODE's. Depending on whether mass conservation is present or not either N*v (_EvalREq) or Nr*v (_EvalREq2) is used to automatically generate the ODE's.
3661        Returns sdot.
3662
3663        Arguments:
3664
3665        s: species vector
3666        Vtemp: rate vector
3667
3668        """
3669        if self.__HAS_RATE_RULES__:
3670            s, self.__rrule__ = numpy.split(s, [self.Nrmatrix.shape[0]])
3671        if self.__HAS_MOIETY_CONSERVATION__ == True:
3672            for x in range(len(s)):
3673                self.__SALL__[self.nrmatrix_row[x]] = s[x]
3674            for x in range(len(self.lzeromatrix_row)):
3675                self.__SALL__[self.lzeromatrix_row[x]] = self.__tvec_a__[
3676                    x
3677                ] + numpy.add.reduce(self.__lzeromatrix__[x, :] * s)
3678            self.__vvec__ = self._EvalREq(self.__SALL__, Vtemp)
3679            s = numpy.add.reduce(self.__nrmatrix__ * self.__vvec__, 1)
3680        else:
3681            self.__vvec__ = self._EvalREq(s, Vtemp)
3682            s = numpy.add.reduce(self.__nmatrix__ * self.__vvec__, 1)
3683        if self.__HAS_RATE_RULES__:
3684            s = numpy.concatenate([s, self.__rrule__]).copy()
3685        return s
3686
3687    # the following are user functions defined in the input file or assigned after instantiated
3688    def _EvalODE_CVODE(self, s, Vtemp):
3689        """
3690        _EvalODE_CVODE(s,Vtemp)
3691
3692        Evaluate the full set of ODE's. Depending on whether mass conservation is present or not
3693        either N*v (_EvalREq) or Nr*v (_EvalREq2_alt) is used.
3694
3695        Arguments:
3696
3697        s: species vector
3698        Vtemp: rate vector
3699
3700        """
3701        if self.__HAS_RATE_RULES__:
3702            s, self.__rrule__ = numpy.split(s, [self.Nmatrix.shape[0]])
3703        self.__vvec__ = self._EvalREq(s, Vtemp)
3704        s = numpy.add.reduce(self.__nmatrix__ * self.__vvec__, 1)
3705        if self.__HAS_RATE_RULES__:
3706            s = numpy.concatenate([s, self.__rrule__]).copy()
3707        return s
3708
3709    def Forcing_Function(self):
3710        """
3711        Forcing_Function()
3712
3713        User defined forcing function either defined in the PSC input file as !F or by overwriting this method.
3714        This method is evaluated prior to every rate equation evaluation.
3715
3716        Arguments:
3717        None
3718
3719        """
3720        # initialized in __initFunction__() - brett 20050621
3721        exec(self.__CODE_forced)
3722
3723    def User_Function(self):
3724        """
3725        **Deprecated**
3726        """
3727        exec(self.__CODE_user)
3728
3729    # pysundials
3730
3731    def _EvalExtraData(self, xdata):
3732        """
3733        Takes a list of model attributes and returns an array of values
3734        """
3735        return numpy.array([getattr(self, d) for d in xdata])
3736
3737    __CVODE_initialise__ = True
3738    CVODE_continuous_result = None
3739    __CVODE_initial_num__ = None
3740
3741    def CVODE_continue(self, tvec):
3742        """
3743        **Experimental:** continues a simulation over a new time vector, the
3744        CVODE memobj is reused and not reinitialised and model parameters can be
3745        changed between calls to this method. The mod.data_sim objects from
3746        the initial simulation and all calls to this method are stored in the list
3747        *mod.CVODE_continuous_result*.
3748
3749         - *tvec* a numpy array of time points
3750
3751        """
3752        assert (
3753            self.mode_integrator == 'CVODE'
3754        ), "\nFor what should be rather obvious reasons, this method requires CVODE to be used as the default integration algorithm.\n"
3755        if self.CVODE_continuous_result == None:
3756            self.CVODE_continuous_result = [self.data_sim]
3757
3758        self.__CVODE_initialise__ = False
3759        self.sim_time = tvec
3760
3761        Tsim0 = time.time()
3762        sim_res, rates, simOK = self.CVODE(None)
3763        Tsim1 = time.time()
3764        print(
3765            "{} time for {} points: {}".format(
3766                self.mode_integrator, len(self.sim_time), Tsim1 - Tsim0
3767            )
3768        )
3769
3770        if self.__HAS_RATE_RULES__:
3771            sim_res, rrules = numpy.split(sim_res, [len(self.__species__)], axis=1)
3772            print('RateRules evaluated and added to mod.data_sim.')
3773
3774        # TODO: split this off into a method shared by this and Simulate()
3775        self.data_sim = IntegrationDataObj()
3776        self.IS_VALID = simOK
3777        self.data_sim.setTime(self.sim_time)
3778        self.data_sim.setSpecies(sim_res, self.__species__)
3779        self.data_sim.setRates(rates, self.__reactions__)
3780        if self.__HAS_RATE_RULES__:
3781            self.data_sim.setRules(rrules, self.__rate_rules__)
3782        if len(self.CVODE_extra_output) > 0:
3783            self.data_sim.setXData(self.CVODE_xdata, lbls=self.CVODE_extra_output)
3784            self.CVODE_xdata = None
3785        if not simOK:
3786            print('Simulation failure')
3787        del sim_res
3788
3789        self.CVODE_continuous_result.append(self.data_sim)
3790        self.__CVODE_initialise__ = True
3791
3792    # def CVODE(self, initial):
3793    #     """
3794    #     CVODE(initial)
3795    #
3796    #     PySCeS interface to the CVODE integration algorithm.
3797    #
3798    #     Arguments:
3799    #     initial: vector containing initial species concentrations
3800    #
3801    #     """
3802    #     assert (
3803    #         _HAVE_ASSIMULO
3804    #     ), '\nPySundials is not installed or did not import correctly\n{}'.format(
3805    #         _ASSIMULO_LOAD_ERROR
3806    #     )
3807    #     Vtemp = numpy.zeros((self.__Nshape__[1]))
3808    #
3809    #     def findi(t, y, ydot, f_data):
3810    #         self._TIME_ = t
3811    #         ydota = self._EvalODE(numpy.array(y), Vtemp)
3812    #         ydot[:] = ydota[:]
3813    #         return 0  # non-zero return indicates error state
3814    #
3815    #     def ffull(t, y, ydot, f_data):
3816    #         self._TIME_ = t
3817    #         ##  ya = numpy.array(y)
3818    #         ydota = self._EvalODE_CVODE(numpy.array(y), Vtemp)  # unreduced ODE's
3819    #         ydot[:] = ydota[:]
3820    #         return 0
3821    #
3822    #     func = None
3823    #     if self.mode_integrate_all_odes:
3824    #         func = ffull
3825    #     else:
3826    #         func = findi
3827    #
3828    #     if self.__CVODE_initialise__:
3829    #         tZero = initial.copy()
3830    #         if self.__HAS_RATE_RULES__:
3831    #             initial, rrules = numpy.split(initial, [self.Nrmatrix.shape[0]])
3832    #             tZero = initial.copy()
3833    #         if self.__HAS_MOIETY_CONSERVATION__ and self.mode_integrate_all_odes:
3834    #             initial = self.Fix_S_indinput(initial, amounts=True)
3835    #             tZero = initial.copy()
3836    #         elif self.__HAS_MOIETY_CONSERVATION__:
3837    #             tZero = self.Fix_S_indinput(tZero, amounts=True)
3838    #         if self.__HAS_RATE_RULES__:
3839    #             initial = numpy.concatenate([initial, rrules])
3840    #             tZero = numpy.concatenate([tZero, rrules])
3841    #     else:
3842    #         tZero = None
3843    #
3844    #     # the following block initialises the cvode integrator, and sets various options
3845    #     if self.__CVODE_initialise__:
3846    #         self.__CVODE_y__ = cvode.NVector(initial.tolist())
3847    #         self.__CVODE_mem__ = cvode.CVodeCreate(
3848    #             cvode.CV_BDF, cvode.CV_NEWTON
3849    #         )  # initialisation with basic options, newtonian solver etc...
3850    #         self.__CVODE_initial_num__ = len(initial)
3851    #     del initial
3852    #     t = cvode.realtype(0.0)
3853    #     rates = numpy.zeros((len(self.sim_time), len(self.__reactions__)))
3854    #     output = None
3855    #     if self.__HAS_RATE_RULES__:
3856    #         output = numpy.zeros(
3857    #             (len(self.sim_time), len(self.__rrule__) + len(self.__species__))
3858    #         )
3859    #     else:
3860    #         output = numpy.zeros((len(self.sim_time), len(self.__species__)))
3861    #
3862    #     CVODE_XOUT = False
3863    #     if len(self.CVODE_extra_output) > 0:
3864    #         out = []
3865    #         for d in self.CVODE_extra_output:
3866    #             if (
3867    #                 hasattr(self, d)
3868    #                 and d
3869    #                 not in self.__species__ + self.__reactions__ + self.__rate_rules__
3870    #             ):
3871    #                 out.append(d)
3872    #             else:
3873    #                 print(
3874    #                     '\nWarning: CVODE is ignoring extra data ({}), it either doesn\'t exist or it\'s a species or rate.\n'.format(
3875    #                         d
3876    #                     )
3877    #                 )
3878    #         if len(out) > 0:
3879    #             self.CVODE_extra_output = out
3880    #             CVODE_XOUT = True
3881    #         del out
3882    #
3883    #     if CVODE_XOUT:
3884    #         self.CVODE_xdata = numpy.zeros(
3885    #             (len(self.sim_time), len(self.CVODE_extra_output))
3886    #         )
3887    #
3888    #     sim_st = 0
3889    #     if self.sim_time[0] == 0.0:
3890    #
3891    #         if self.__CVODE_initialise__:
3892    #             out0 = tZero[:].copy()
3893    #         else:
3894    #             out0 = numpy.array(self.__CVODE_y__[:])
3895    #         output[0, :] = out0
3896    #
3897    #         if not self.mode_integrate_all_odes:
3898    #             self._EvalODE(out0.copy(), self._CVODE_Vtemp)
3899    #         else:
3900    #             self._EvalODE_CVODE(out0.copy(), self._CVODE_Vtemp)
3901    #         rates[0] = self.__vvec__
3902    #
3903    #         if CVODE_XOUT:
3904    #             self.CVODE_xdata[0, :] = self._EvalExtraData(self.CVODE_extra_output)
3905    #         sim_st = 1
3906    #     del tZero
3907    #
3908    #     var_store = {}
3909    #     if self.__HAS_EVENTS__:
3910    #         for ev in self.__events__:
3911    #             ev.reset()
3912    #             for ass in ev.assignments:
3913    #                 var_store.update({ass.variable: getattr(self, ass.variable)})
3914    #     TOL_ADJUSTER = 0
3915    #     MAX_TOL_CNT = 5
3916    #     MAX_REL_TOLERANCE = 1.0e-3
3917    #     RELTOL_ADJUST_FACTOR = 1.0e3
3918    #     MIN_ABS_TOL = self.__settings__["cvode_abstol"]  # 1.0e-15
3919    #     ##  MAX_ABS_TOL = self.__settings__["cvode_abstol_max"] #1.0e-3 not used anymore
3920    #     ABSTOL_ADJUST_FACTOR = self.__settings__["cvode_abstol_factor"]  # 1.0e-6
3921    #     cvode_sim_range = list(range(sim_st, len(self.sim_time)))
3922    #     cvode_scale_range = list(
3923    #         range(
3924    #             sim_st,
3925    #             len(self.sim_time),
3926    #             len(self.sim_time) // 4 or len(self.sim_time),
3927    #         )
3928    #     )
3929    #     cvode_scale_range = cvode_scale_range[1:]
3930    #     reltol = cvode.realtype(
3931    #         self.__settings__["cvode_reltol"]
3932    #     )  # relative tolerance must be a realtype
3933    #     abstol = cvode.NVector(
3934    #         self.__CVODE_initial_num__ * [self.__settings__["cvode_abstol"]]
3935    #     )
3936    #     for s in range(len(self.__CVODE_y__)):
3937    #         newVal = abs(self.__CVODE_y__[s]) * ABSTOL_ADJUST_FACTOR
3938    #         if newVal < MIN_ABS_TOL:
3939    #             abstol[s] = MIN_ABS_TOL
3940    #         else:
3941    #             abstol[s] = newVal
3942    #     if self.__CVODE_initialise__:
3943    #         cvode.CVodeMalloc(
3944    #             self.__CVODE_mem__,
3945    #             func,
3946    #             0.0,
3947    #             self.__CVODE_y__,
3948    #             cvode.CV_SV,
3949    #             reltol,
3950    #             abstol,
3951    #         )  # set tolerances, specify vector of initial conditions, set integrtion function etc...
3952    #         cvode.CVDense(
3953    #             self.__CVODE_mem__, self.__CVODE_initial_num__
3954    #         )  # set dense option, specify dimension of problem (3)
3955    #     if self.__HAS_EVENTS__:
3956    #         cvode.CVodeRootInit(
3957    #             self.__CVODE_mem__, len(self.__events__), self.CVODE_EVENTS, None
3958    #         )  # specify to 'root' conditions, and function that calculates them
3959    #
3960    #     for st in cvode_sim_range:
3961    #         tout = self.sim_time[st]
3962    #         errcount = 0
3963    #         ##  print TOL_ADJUSTER, MAX_TOL_CNT, abstol, MAX_REL_TOLERANCE
3964    #         if (
3965    #             self.__settings__["cvode_auto_tol_adjust"]
3966    #             and TOL_ADJUSTER >= MAX_TOL_CNT
3967    #             and reltol.value < MAX_REL_TOLERANCE
3968    #         ):
3969    #             reltol.value = reltol.value * RELTOL_ADJUST_FACTOR
3970    #             cvode.CVodeSetTolerances(
3971    #                 self.__CVODE_mem__, cvode.CV_SV, reltol, abstol
3972    #             )
3973    #             ##  print '\nCVODE: new tolerance set:\nreltol={}'.format(reltol.value)
3974    #             ##  print '\nAbs tolerance:\n{}'.format(abstol)
3975    #             TOL_ADJUSTER = 0
3976    #         if (st in cvode_scale_range) and self.__settings__["cvode_auto_tol_adjust"]:
3977    #             for s in range(len(self.__CVODE_y__)):
3978    #                 newVal = abs(self.__CVODE_y__[s]) * ABSTOL_ADJUST_FACTOR
3979    #                 if newVal < MIN_ABS_TOL:
3980    #                     abstol[s] = MIN_ABS_TOL
3981    #                 else:
3982    #                     abstol[s] = newVal
3983    #             ##  print '\nCVODE: new tolerance set, abstol:\n{}'.format(abstol)
3984    #             cvode.CVodeSetTolerances(
3985    #                 self.__CVODE_mem__, cvode.CV_SV, reltol, abstol
3986    #             )
3987    #             ##  cvode.CVodeReInit(self.__CVODE_mem__, func, self.sim_time[st-1], self.__CVODE_y__, cvode.CV_SV, reltol, abstol)
3988    #         while True:
3989    #             try:
3990    #                 flag = cvode.CVode(
3991    #                     self.__CVODE_mem__,
3992    #                     tout,
3993    #                     self.__CVODE_y__,
3994    #                     cvode.ctypes.byref(t),
3995    #                     cvode.CV_NORMAL,
3996    #                 )
3997    #             except AssertionError as ex:
3998    #                 print('cvode error1', ex)
3999    #                 flag = None
4000    #             self._TIME_ = tout
4001    #             if (
4002    #                 flag == cvode.CV_ROOT_RETURN
4003    #             ):  # if a root was found before desired time point, output it
4004    #                 ya = numpy.array(self.__CVODE_y__)
4005    #                 rootsfound = cvode.CVodeGetRootInfo(
4006    #                     self.__CVODE_mem__, len(self.__events__)
4007    #                 )
4008    #                 reInit = False
4009    #                 for ev in range(len(self.__events__)):
4010    #                     if rootsfound[ev] == 1:
4011    #                         for ass in self.__events__[ev].assignments:
4012    #                             # only can assign to independent species vector
4013    #                             if ass.variable in self.L0matrix.getLabels()[1] or (
4014    #                                 self.mode_integrate_all_odes
4015    #                                 and ass.variable in self.__species__
4016    #                             ):
4017    #                                 assVal = ass.getValue()
4018    #                                 assIdx = self.__species__.index(ass.variable)
4019    #                                 if self.__KeyWords__['Species_In_Conc']:
4020    #                                     ##  print self.__CVODE_y__
4021    #                                     self.__CVODE_y__[assIdx] = assVal * getattr(
4022    #                                         self, self.__CsizeAllIdx__[assIdx]
4023    #                                     )
4024    #                                     ##  raw_input(self.__CVODE_y__)
4025    #                                 else:
4026    #                                     self.__CVODE_y__[assIdx] = assVal
4027    #                                 reInit = True
4028    #                             elif (
4029    #                                 not self.mode_integrate_all_odes
4030    #                                 and ass.variable in self.L0matrix.getLabels()[0]
4031    #                             ):
4032    #                                 print(
4033    #                                     'Event assignment to dependent species consider setting \"mod.mode_integrate_all_odes = True\"'
4034    #                                 )
4035    #                             elif (
4036    #                                 self.__HAS_RATE_RULES__
4037    #                                 and ass.variable in self.__rate_rules__
4038    #                             ):
4039    #                                 ##  print 'Event is assigning to rate rule'
4040    #                                 assVal = ass.getValue()
4041    #                                 rrIdx = self.__rate_rules__.index(ass.variable)
4042    #                                 ##  print ass.variable, assVal
4043    #                                 self.__rrule__[rrIdx] = assVal
4044    #                                 ##  print self.L0matrix.shape[1], rrIdx, len(self.__CVODE_y__)
4045    #                                 self.__CVODE_y__[
4046    #                                     self.L0matrix.shape[1] + rrIdx
4047    #                                 ] = assVal
4048    #                                 setattr(self, ass.variable, assVal)
4049    #                                 reInit = True
4050    #                             else:
4051    #                                 try:
4052    #                                     setattr(self, ass.variable, ass.getValue())
4053    #                                     reInit = True
4054    #                                 except:
4055    #                                     print(
4056    #                                         'ERROR: Updating model attribute from event: ',
4057    #                                         ass.variable,
4058    #                                     )
4059    #
4060    #                 if reInit:
4061    #                     cvode.CVodeReInit(
4062    #                         self.__CVODE_mem__,
4063    #                         func,
4064    #                         tout,
4065    #                         self.__CVODE_y__,
4066    #                         cvode.CV_SV,
4067    #                         reltol,
4068    #                         abstol,
4069    #                     )
4070    #
4071    #                 # this gets everything into the current tout state
4072    #                 tmp = None
4073    #                 if not self.mode_integrate_all_odes:
4074    #                     tmp = self._EvalODE(ya.copy(), self._CVODE_Vtemp)
4075    #                 else:
4076    #                     tmp = self._EvalODE_CVODE(ya.copy(), self._CVODE_Vtemp)
4077    #                 del tmp
4078    #
4079    #                 # here we regenerate Sd's and fix concentrations
4080    #                 rrules = None
4081    #                 if (
4082    #                     self.__HAS_MOIETY_CONSERVATION__
4083    #                     and not self.mode_integrate_all_odes
4084    #                 ):
4085    #                     if self.__HAS_RATE_RULES__:
4086    #                         ya, rrules = numpy.split(ya, [self.Nrmatrix.shape[0]])
4087    #                     ya = self.Fix_S_indinput(ya, amounts=True)
4088    #                     # convert to concentrations
4089    #                     if (
4090    #                         self.__HAS_COMPARTMENTS__
4091    #                         and self.__KeyWords__['Output_In_Conc']
4092    #                     ):
4093    #                         ya = self._SpeciesAmountToConc(ya)
4094    #                     if self.__HAS_RATE_RULES__:
4095    #                         ya = numpy.concatenate([ya, rrules])
4096    #                 else:
4097    #                     if self.__HAS_RATE_RULES__:
4098    #                         ya, rrules = numpy.split(ya, [self.Nmatrix.shape[0]])
4099    #                     if (
4100    #                         self.__HAS_COMPARTMENTS__
4101    #                         and self.__KeyWords__['Output_In_Conc']
4102    #                     ):
4103    #                         ya = self._SpeciesAmountToConc(ya)
4104    #                     if self.__HAS_RATE_RULES__:
4105    #                         ya = numpy.concatenate([ya, rrules])
4106    #
4107    #                 output[st] = ya
4108    #                 # set with self._EvalODE above
4109    #                 rates[st] = self.__vvec__
4110    #
4111    #                 if CVODE_XOUT:
4112    #                     self.CVODE_xdata[st, :] = self._EvalExtraData(
4113    #                         self.CVODE_extra_output
4114    #                     )
4115    #                 # this should adjust the expected time to the new output time time
4116    #                 self.sim_time[st] = float(tout)
4117    #                 # dont need anymore i think
4118    #                 if _HAVE_VPYTHON:
4119    #                     self.CVODE_VPYTHON(ya)
4120    #                 del ya, rrules
4121    #                 break
4122    #             if flag == cvode.CV_SUCCESS:
4123    #                 ya = numpy.array(self.__CVODE_y__)
4124    #                 # this gets everything into the current tout state
4125    #                 tmp = None
4126    #                 if not self.mode_integrate_all_odes:
4127    #                     tmp = self._EvalODE(ya.copy(), self._CVODE_Vtemp)
4128    #                 else:
4129    #                     tmp = self._EvalODE_CVODE(ya.copy(), self._CVODE_Vtemp)
4130    #                 del tmp
4131    #                 # here we regenerate Sd's and fix concentrations
4132    #                 rrules = None
4133    #                 if (
4134    #                     self.__HAS_MOIETY_CONSERVATION__
4135    #                     and not self.mode_integrate_all_odes
4136    #                 ):
4137    #                     if self.__HAS_RATE_RULES__:
4138    #                         ya, rrules = numpy.split(ya, [self.Nrmatrix.shape[0]])
4139    #                     ya = self.Fix_S_indinput(ya, amounts=True)
4140    #                     # convert to concentrations
4141    #                     if (
4142    #                         self.__HAS_COMPARTMENTS__
4143    #                         and self.__KeyWords__['Output_In_Conc']
4144    #                     ):
4145    #                         ya = self._SpeciesAmountToConc(ya)
4146    #                     if self.__HAS_RATE_RULES__:
4147    #                         ya = numpy.concatenate([ya, rrules])
4148    #                 else:
4149    #                     if self.__HAS_RATE_RULES__:
4150    #                         ya, rrules = numpy.split(ya, [self.Nmatrix.shape[0]])
4151    #                     if (
4152    #                         self.__HAS_COMPARTMENTS__
4153    #                         and self.__KeyWords__['Output_In_Conc']
4154    #                     ):
4155    #                         ya = self._SpeciesAmountToConc(ya)
4156    #                     if self.__HAS_RATE_RULES__:
4157    #                         ya = numpy.concatenate([ya, rrules])
4158    #
4159    #                 output[st] = ya
4160    #                 # set with self._EvalODE above
4161    #                 rates[st] = self.__vvec__
4162    #
4163    #                 if CVODE_XOUT:
4164    #                     self.CVODE_xdata[st, :] = self._EvalExtraData(
4165    #                         self.CVODE_extra_output
4166    #                     )
4167    #                 if _HAVE_VPYTHON:
4168    #                     self.CVODE_VPYTHON(ya)
4169    #                 del ya, rrules
4170    #                 break
4171    #             elif flag == -1:
4172    #                 if self.__settings__["cvode_mxstep"] == 1000:
4173    #                     self.__settings__["cvode_mxstep"] = 3000
4174    #                     TOL_ADJUSTER += 1
4175    #                 elif self.__settings__["cvode_mxstep"] == 3000:
4176    #                     self.__settings__["cvode_mxstep"] = 10000
4177    #                     TOL_ADJUSTER += 2
4178    #                 elif self.__settings__["cvode_mxstep"] == 10000:
4179    #                     ##  TOL_ADJUSTER += 1
4180    #                     output[st] = numpy.NaN
4181    #                     break
4182    #                 print(
4183    #                     'mxstep warning ({}) mxstep set to {}'.format(
4184    #                         flag, self.__settings__["cvode_mxstep"]
4185    #                     )
4186    #                 )
4187    #                 cvode.CVodeSetMaxNumSteps(
4188    #                     self.__CVODE_mem__, self.__settings__["cvode_mxstep"]
4189    #                 )
4190    #             elif flag < -3:
4191    #                 print('CVODE error:', flag)
4192    #                 print('At ', tout)
4193    #                 output[st] = numpy.NaN
4194    #                 rates[st] = numpy.NaN
4195    #                 if CVODE_XOUT:
4196    #                     self.CVODE_xdata[st] = numpy.NaN
4197    #                 break
4198    #         self.__settings__["cvode_mxstep"] = 1000
4199    #         cvode.CVodeSetMaxNumSteps(
4200    #             self.__CVODE_mem__, self.__settings__["cvode_mxstep"]
4201    #         )
4202    #
4203    #     if self.__HAS_EVENTS__:
4204    #         for ass in list(var_store.keys()):
4205    #             # print 'old value', ass, getattr(self, ass)
4206    #             setattr(self, ass, var_store[ass])
4207    #             # print 'new value', getattr(self, ass)
4208    #
4209    #     if self.__settings__["cvode_stats"]:
4210    #         # print some stats from the intgrator
4211    #         nst = cvode.CVodeGetNumSteps(self.__CVODE_mem__)
4212    #         nfe = cvode.CVodeGetNumRhsEvals(self.__CVODE_mem__)
4213    #         nsetups = cvode.CVodeGetNumLinSolvSetups(self.__CVODE_mem__)
4214    #         netf = cvode.CVodeGetNumErrTestFails(self.__CVODE_mem__)
4215    #         nni = cvode.CVodeGetNumNonlinSolvIters(self.__CVODE_mem__)
4216    #         ncfn = cvode.CVodeGetNumNonlinSolvConvFails(self.__CVODE_mem__)
4217    #         nje = cvode.CVDenseGetNumJacEvals(self.__CVODE_mem__)
4218    #         nfeLS = cvode.CVDenseGetNumRhsEvals(self.__CVODE_mem__)
4219    #         nge = cvode.CVodeGetNumGEvals(self.__CVODE_mem__)
4220    #
4221    #         print("\nFinal Statistics:")
4222    #         print(
4223    #             "nst = {} nfe  = {} nsetups = {} nfeLS = {} nje = {}".format(
4224    #                 nst, nfe, nsetups, nfeLS, nje
4225    #             )
4226    #         )
4227    #         print(
4228    #             "nni = {} ncfn = {} netf = {} nge = {}\n ".format(nni, ncfn, netf, nge)
4229    #         )
4230    #         print('reltol = {}'.format(reltol))
4231    #         print('abstol:\n{}'.format(abstol))
4232    #
4233    #     if cvode.CV_SUCCESS >= 0:
4234    #         return output, rates, True
4235    #     else:
4236    #         return output, rates, False
4237
4238    def CVODE(self, initial):
4239        """
4240        CVODE(initial)
4241
4242        PySCeS interface to the CVode integration algorithm. Given a set of initial
4243        conditions.
4244
4245        Arguments:
4246
4247        initial: vector containing initial species concentrations
4248
4249        """
4250        assert (
4251            _HAVE_ASSIMULO
4252        ), '\nAssimulo is not installed or did not import correctly\n{}'.format(
4253            _ASSIMULO_LOAD_ERROR
4254        )
4255        Vtemp = numpy.zeros((self.__Nshape__[1]), 'd')
4256
4257        def findi(t, s):
4258            self._TIME_ = t
4259            return self._EvalODE(s, Vtemp)
4260
4261        def ffull(t, s):
4262            self._TIME_ = t
4263            return self._EvalODE_CVODE(s, Vtemp)  # unreduced ODE's
4264
4265        if self.mode_integrate_all_odes:
4266            rhs = ffull
4267        else:
4268            rhs = findi
4269
4270        if self.__CVODE_initialise__:
4271            if self.__HAS_RATE_RULES__:
4272                initial, rrules = numpy.split(initial, [self.Nrmatrix.shape[0]])
4273            if self.__HAS_MOIETY_CONSERVATION__ and self.mode_integrate_all_odes:
4274                initial = self.Fix_S_indinput(initial, amounts=True)
4275            if self.__HAS_RATE_RULES__:
4276                initial = numpy.concatenate([initial, rrules])
4277
4278        problem = EventsProblem(self, rhs=rhs, y0=initial)
4279        # for direct access to the problem class
4280        self._problem = problem
4281        sim = CVode(problem)
4282        # for direct access to the solver class
4283        self._solver = sim
4284        if self.__settings__["cvode_stats"]:
4285            sim.verbosity = 10
4286        else:
4287            sim.verbosity = 40
4288        sim.atol = self.__settings__["cvode_abstol"]
4289        sim.rtol = self.__settings__["cvode_reltol"]
4290        t, sim_res = sim.simulate(self.sim_end, ncp=0, ncp_list=self.sim_time)
4291        # needed because CVode adds extra time points around discontinuity
4292        t = numpy.array(t)
4293        # divide m.sim_time into segments between event firings
4294        idx = [0] + [numpy.max(numpy.where(t == i)) for i in
4295                     problem.event_times] + [len(t)]
4296
4297        # initialise rates array
4298        rates = numpy.zeros((sim_res.shape[0], len(self.__reactions__)))
4299
4300        if (
4301                self.__HAS_MOIETY_CONSERVATION__
4302                and not self.mode_integrate_all_odes
4303        ):
4304            # calculate rates from independent species
4305            # re-assign parameters after every event in case they changed
4306            for i in range(len(idx) - 1):
4307                for j in range(len(self.parameters)):
4308                    setattr(self, self.parameters[j], problem.parvals[i][j])
4309                for r in range(idx[i], idx[i + 1]):
4310                    self._EvalODE(sim_res[r].copy(), self._CVODE_Vtemp)
4311                    rates[r] = self.__vvec__
4312            if self.__HAS_RATE_RULES__:
4313                sim_res, rrules = numpy.split(sim_res, [self.Nmatrix.shape[0]], axis=1)
4314            # regenerate dependent variables
4315            res = numpy.zeros((sim_res.shape[0], len(self.__species__)))
4316            for x in range(sim_res.shape[0]):
4317                res[x, :] = self.Fix_S_indinput(sim_res[x, :], amounts=True)
4318            sim_res = res
4319            del res
4320            # convert to concentrations
4321            if (
4322                    self.__HAS_COMPARTMENTS__
4323                    and self.__KeyWords__['Output_In_Conc']
4324            ):
4325                for x in range(0, sim_res.shape[0]):
4326                    sim_res[x] = self._SpeciesAmountToConc(sim_res[x])
4327            if self.__HAS_RATE_RULES__:
4328                sim_res = numpy.concatenate([sim_res, rrules], axis=1)
4329
4330        else:
4331            # calculate rates from all species
4332            # re-assign parameters after every event in case they changed
4333            for i in range(len(idx) - 1):
4334                for j in range(len(self.parameters)):
4335                    setattr(self, self.parameters[j], problem.parvals[i][j])
4336                for r in range(idx[i], idx[i + 1]):
4337                    self._EvalODE_CVODE(sim_res[r].copy(), self._CVODE_Vtemp)
4338                    rates[r] = self.__vvec__
4339            if self.__HAS_RATE_RULES__:
4340                sim_res, rrules = numpy.split(sim_res, [self.Nmatrix.shape[0]], axis=1)
4341            if (
4342                    self.__HAS_COMPARTMENTS__
4343                    and self.__KeyWords__['Output_In_Conc']
4344            ):
4345                for x in range(sim_res.shape[0]):
4346                    sim_res[x] = self._SpeciesAmountToConc(sim_res[x])
4347            if self.__HAS_RATE_RULES__:
4348                sim_res = numpy.concatenate([sim_res, rrules], axis=1)
4349
4350        if self.__settings__['cvode_return_event_timepoints']:
4351            self.sim_time = t
4352        else:
4353            tidx = [numpy.where(t==i)[0][0] for i in self.sim_time]
4354            sim_res = sim_res[tidx]
4355            rates = rates[tidx]
4356
4357        return sim_res, rates, True
4358
4359    def CVODE_VPYTHON(self, s):
4360        """Future VPython hook for CVODE"""
4361        pass
4362
4363    def LSODA(self, initial):
4364        """
4365        LSODA(initial)
4366
4367        PySCeS interface to the LSODA integration algorithm. Given a set of initial
4368        conditions LSODA returns an array of species concentrations and a status flag.
4369        LSODA controls are accessible as mod.lsoda_<control>
4370
4371        Arguments:
4372
4373        initial: vector containing initial species concentrations
4374
4375        """
4376        Vtemp = numpy.zeros((self.__Nshape__[1]), 'd')
4377
4378        def function_sim(s, t):
4379            self._TIME_ = t
4380            return self._EvalODE(s, Vtemp)
4381
4382        iter = 0
4383        go = True
4384
4385        status = 0
4386        while go:
4387            sim_res, infodict = scipy.integrate.odeint(
4388                function_sim,
4389                initial,
4390                self.sim_time,
4391                atol=self.__settings__['lsoda_atol'],
4392                rtol=self.__settings__['lsoda_rtol'],
4393                mxstep=self.__settings__["lsoda_mxstep"],
4394                h0=self.__settings__["lsoda_h0"],
4395                hmax=self.__settings__["lsoda_hmax"],
4396                hmin=self.__settings__["lsoda_hmin"],
4397                mxordn=self.__settings__["lsoda_mxordn"],
4398                mxords=self.__settings__["lsoda_mxords"],
4399                printmessg=self.__settings__["lsoda_mesg"],
4400                full_output=1,
4401            )
4402            if infodict['message'] == 'Integration successful.':
4403                status = 0
4404            else:
4405                status = 1
4406            if status > 0 and iter < self.__settings__['mode_sim_max_iter']:
4407                if self.__settings__["lsoda_mxstep"] == 0:
4408                    print(
4409                        '\nIntegration error\n\nSetting self.__settings__["lsoda_mxstep"] = 1000 and reSimulating ...'
4410                    )
4411                    self.__settings__["lsoda_mxstep"] = 1000
4412                else:
4413                    print(
4414                        'Integration error\n\nSetting self.__settings__["lsoda_mxstep"] = '
4415                        + repr(self.__settings__["lsoda_mxstep"] * 3)
4416                        + ' and reSimulating ...'
4417                    )
4418                    self.__settings__["lsoda_mxstep"] = (
4419                        self.__settings__["lsoda_mxstep"] * 3
4420                    )
4421                iter += 1
4422            elif status > 0 and iter == self.__settings__['mode_sim_max_iter']:
4423                print(
4424                    '\nThis simulation is going nowhere fast\nConsider trying CVODE (mod.mode_integrator = \'CVODE\')\n'
4425                )
4426                print(
4427                    'self.__settings__["lsoda_mxstep"] = '
4428                    + repr(self.__settings__["lsoda_mxstep"])
4429                )
4430                print('__settings__[\'mode_sim_max_iter\'] = ' + repr(iter))
4431                go = False
4432            else:
4433                go = False
4434        self.__settings__["lsoda_mxstep"] = 0
4435
4436        rates = numpy.zeros((sim_res.shape[0], len(self.__reactions__)))
4437        if status == 0:
4438
4439            tmp = None
4440            for r in range(sim_res.shape[0]):
4441                tmp = self._EvalODE(sim_res[r].copy(), self._CVODE_Vtemp)
4442                # set with self._EvalODE above
4443                rates[r] = self.__vvec__
4444            del tmp
4445
4446            # regenerate dependent variables
4447            if self.__HAS_RATE_RULES__:
4448                sim_res, rrules = numpy.split(sim_res, [self.Nrmatrix.shape[0]], axis=1)
4449            if self.__HAS_MOIETY_CONSERVATION__ == True:
4450                res = numpy.zeros((sim_res.shape[0], len(self.__species__)))
4451                for x in range(0, sim_res.shape[0]):
4452                    res[x, :] = self.Fix_S_indinput(sim_res[x, :], amounts=True)
4453                sim_res = res
4454                del res
4455            if self.__HAS_COMPARTMENTS__ and self.__KeyWords__['Output_In_Conc']:
4456                for x in range(0, sim_res.shape[0]):
4457                    sim_res[x] = self._SpeciesAmountToConc(sim_res[x])
4458
4459            if self.__HAS_RATE_RULES__:
4460                sim_res = numpy.concatenate([sim_res, rrules], axis=1)
4461            return sim_res, rates, True
4462        else:
4463            if self.__HAS_MOIETY_CONSERVATION__ == True and self.__HAS_RATE_RULES__:
4464                sim_res = numpy.zeros(
4465                    (sim_res.shape[0], len(self.__species__) + len(self.__rrule__)), 'd'
4466                )
4467            elif self.__HAS_MOIETY_CONSERVATION__ == True:
4468                sim_res = numpy.zeros((sim_res.shape[0], len(self.__species__)), 'd')
4469            sim_res[:] = scipy.NaN
4470            return sim_res, rates, False
4471
4472    def HYBRD(self, initial):
4473        """
4474        HYBRD(initial)
4475
4476        PySCeS interface to the HYBRD solver. Returns a steady-state solution and
4477        error flag. Good general purpose solver.
4478        Algorithm controls are available as mod.hybrd_<control>
4479
4480        Arguments:
4481
4482        initial: vector of initial species concentrations
4483
4484        """
4485        Vtemp = numpy.zeros((self.__Nshape__[1]), 'd')
4486
4487        def function_state(s):
4488            return self._EvalODE(s, Vtemp)
4489
4490        state_out = scipy.optimize.fsolve(
4491            function_state,
4492            initial,
4493            args=(),
4494            xtol=self.__settings__['hybrd_xtol'],
4495            maxfev=self.__settings__['hybrd_maxfev'],
4496            epsfcn=self.__settings__['hybrd_epsfcn'],
4497            factor=self.__settings__['hybrd_factor'],
4498            col_deriv=0,
4499            full_output=1,
4500        )
4501        if state_out[2] == 1:
4502            if self.__settings__['hybrd_mesg'] == 1:
4503                print('(hybrd)', state_out[3])
4504            return state_out[0], True
4505        else:
4506            if self.__settings__['hybrd_mesg']:
4507                print('INFO: (hybrd) Invalid steady state:')
4508            if self.__settings__['hybrd_mesg'] == 1:
4509                print('(hybrd)', state_out[3])
4510            return state_out[0], False
4511
4512    def FINTSLV(self, initial):
4513        """
4514        FINTSLV(initial)
4515
4516        Forward integration steady-state solver. Finds a steady state when the
4517        maximum change in species concentration falls within a specified tolerance.
4518        Returns the steady-state solution and a error flag.
4519        Algorithm controls are available as mod.fintslv_<control>
4520
4521        Arguments:
4522
4523        initial: vector of initial concentrations
4524
4525        """
4526        sim_time = self.__fintslv_range__ * self.__settings__['fintslv_rmult']
4527        # print sim_time
4528        Vtemp = numpy.zeros((self.__Nshape__[1]), 'd')
4529
4530        def function_sim(s, t):
4531            return self._EvalODE(s, Vtemp)
4532
4533        res, infodict = scipy.integrate.odeint(
4534            function_sim,
4535            initial.copy(),
4536            sim_time,
4537            atol=self.__settings__['lsoda_atol'],
4538            rtol=self.__settings__[
4539                'lsoda_rtol'
4540            ],  ##  mxstep=self.__settings__["lsoda_mxstep"],\
4541            mxstep=10000,
4542            h0=self.__settings__["lsoda_h0"],
4543            hmax=self.__settings__["lsoda_hmax"],
4544            hmin=self.__settings__["lsoda_hmin"],
4545            mxordn=self.__settings__["lsoda_mxordn"],
4546            mxords=self.__settings__["lsoda_mxords"],
4547            printmessg=self.__settings__["lsoda_mesg"],
4548            full_output=1,
4549        )
4550        if infodict['message'] == 'Integration successful.':
4551            status = True
4552        else:
4553            status = False
4554
4555        # run through results if max(abs([x]-[x-1])) < self.__settings__['fintslv_tol'] score +1
4556        # if you get 5 points by seq end ... happiness
4557        OK = 0
4558        if status:
4559            for x in range(len(res)):
4560                if OK >= self.__settings__['fintslv_step']:
4561                    break
4562                if x > 0 and OK < self.__settings__['fintslv_step']:
4563                    dif = abs(res[x] - res[x - 1])
4564                    if max(dif) < self.__settings__['fintslv_tol']:
4565                        OK += 1
4566                    else:
4567                        pass
4568        if OK >= 5:
4569            return res[-1], True
4570        else:
4571            return res[-1], False
4572
4573    def NLEQ2(self, initial):
4574        """
4575        NLEQ2(initial)
4576
4577        PySCeS interface to the (optional) NLEQ2 algorithm. This is a powerful steady-state
4578        solver that can usually find a solution for when HYBRD() fails. Algorithm
4579        controls are available as: mod.nleq2_<control>
4580        Returns as steady-state solution and error flag.
4581
4582        Arguments:
4583
4584        initial: vector of initial species concentrations
4585
4586        """
4587        Vtemp = numpy.zeros((self.__Nshape__[1]), 'd')
4588        initial0 = initial.copy()
4589        s_scale = initial.copy()
4590
4591        N = len(initial)
4592        iwk = numpy.zeros((N + 52), 'i')
4593        rwk = numpy.zeros(((N + max(N, 10) + 15) * N + 61), 'd')
4594        iopt = numpy.zeros((50), 'i')
4595
4596        if self.__settings__['nleq2_jacgen'] == 1:
4597            print(
4598                '(nleq2)User supplied Jacobian not supported yet ... setting __settings__[\'nleq2_jacgen\'] = 0'
4599            )
4600            self.__settings__['nleq2_jacgen'] = 0
4601
4602        rtol = mach_spec.eps * 10.0 * N
4603        iopt[2] = self.__settings__['nleq2_jacgen']  # 2
4604        iopt[8] = self.__settings__['nleq2_iscaln']  # 0
4605        iopt[10] = 0
4606        iopt[11] = 6
4607        iopt[12] = 0
4608        iopt[13] = 6
4609        iopt[14] = 0
4610        iopt[15] = 6
4611        iopt[30] = self.__settings__['nleq2_nonlin']  # 4
4612        iopt[31] = self.__settings__['nleq2_qrank1']  # 1
4613        iopt[34] = self.__settings__['nleq2_qnscal']  # 0
4614        iopt[37] = self.__settings__['nleq2_ibdamp']  # 0
4615        iopt[38] = self.__settings__['nleq2_iormon']  # 2
4616
4617        iwk[30] = self.nleq2_nitmax
4618
4619        def func(s, ifail):
4620            s = self._EvalODE(s, Vtemp)
4621            if numpy.isnan(s).any():
4622                ifail = -1
4623            elif (numpy.abs(s) > 1.0e150).any():
4624                ifail = -1
4625            return s, ifail
4626
4627        def jacfunc(s):
4628            return s
4629
4630        ierr = 0
4631
4632        BRETT_DEBUG_MODE = False
4633        ADVANCED_MODE = self.__settings__['nleq2_advanced_mode']
4634        if ADVANCED_MODE:
4635            max_iter = self.__settings__['nleq2_iter']  # 3
4636            max_iter_ceiling = self.__settings__['nleq2_iter_max']  # 10
4637        else:
4638            max_iter_ceiling = self.__settings__['nleq2_iter']
4639            max_iter = self.__settings__['nleq2_iter']
4640
4641        iter = 1
4642        GO = True
4643        while GO:
4644            if BRETT_DEBUG_MODE:
4645                print('s_scale', s_scale)
4646                print('ierr({}) = {}'.format(iter, ierr))
4647                print('rtol({}) = {}'.format(iter, rtol))
4648                print('nitmax({}) = {}'.format(iter, iwk[30]))
4649                print('s_scale({}) = {}'.format(iter, s_scale))
4650                print('max_iter({}) = {}'.format(iter, max_iter))
4651
4652            res, s_scale, rtol, iopt, ierr = nleq2.nleq2(
4653                func, jacfunc, initial, s_scale, rtol, iopt, iwk, rwk
4654            )
4655
4656            if ierr == 0:
4657                # success
4658                GO = False
4659            elif ierr == 21:
4660                # negative rtol
4661                GO = False
4662                ##  rtol = abs(rtol)
4663                ##  ierr = 0
4664            elif ierr == 2:
4665                # nitmax reached
4666                iwk[30] = iwk[30] * self.__settings__['nleq2_growth_factor']  # 10
4667
4668            if iter >= max_iter and iter >= max_iter_ceiling:
4669                GO = False
4670            iter += 1
4671
4672        if BRETT_DEBUG_MODE and ierr > 0:
4673            print('ierr = {}'.format(ierr))
4674            print(res)
4675            time.sleep(5)
4676
4677        if ierr > 0:
4678            if self.__settings__['nleq2_mesg']:
4679                print('(nleq2) exits with ierr = {}'.format(ierr))
4680        else:
4681            if self.__settings__['nleq2_mesg']:
4682                print('(nleq2) The solution converged.')
4683        if ierr > 0:
4684            return res, False
4685        else:
4686            return res, True
4687
4688    def PITCON(self, scanpar, scanpar3d=None):
4689        """
4690        PITCON(scanpar,scanpar3d=None)
4691
4692        PySCeS interface to the PITCON continuation algorithm. Single parameter
4693        continuation has been implemented as a "scan" with the continuation
4694        being initialised in mod.pitcon_par_space. The second argument does not
4695        affect the continuation but can be used to insert a third axis parameter into
4696        the results. Returns an array containing the results.
4697        Algorithm controls are available as mod.pitcon_<control>
4698
4699        Arguments:
4700
4701        scanpar: the model parameter to scan (x5)
4702        scanpar3d [default=None]: additional output parameter for 3D plots
4703
4704        """
4705        if self.__HAS_RATE_RULES__:
4706            raise NotImplementedError(
4707                '\nBifurcation analysis not currently available for models containing RateRules'
4708            )
4709
4710        assert (
4711            type(scanpar) == str
4712        ), '\nscanpar must be a <string> representing a model parameter'
4713        modpar = list(self.__parameters__)
4714        try:
4715            a = modpar.index(scanpar)
4716        except:
4717            raise NameError(repr(scanpar) + ' is not a parameter of this model')
4718        if scanpar3d != None:
4719            if type(scanpar3d) == str:
4720                scanpar3d = float(scanpar3d)
4721                assert type(scanpar3d) == float, 'scanpar3d must be a <float>'
4722
4723        par_hold = getattr(self, scanpar)
4724
4725        if self.__settings__["pitcon_jac_opt"] < 1:
4726            self.__settings__["pitcon_jac_opt"] = 1
4727            print(
4728                '\nINFO: .__settings__["pitcon_jac_opt"] set to 1 - user defined jacobian function not yet supported'
4729            )
4730
4731        # DONE!
4732        def fx(s):
4733            setattr(self, scanpar, s[-1])
4734            try:
4735                sdot[:-1] = self._EvalODE(s[:-1], Vtemp)
4736            except Exception as ex:
4737                print('PITCON EXCEPTION 1', ex)
4738                sdot[:-1] = 0.0
4739            sdot[-1] = s[-1]
4740            return sdot
4741
4742        parm = len(self.__SI__) + 1
4743        Vtemp = numpy.zeros((self.__Nshape__[1]), 'd')
4744        xr2 = numpy.zeros((parm), 'd')
4745        sdot = numpy.zeros((parm), 'd')
4746        iwork = numpy.zeros((30 + parm), 'i')
4747        rwork = numpy.zeros((30 + (6 * (parm)) * (parm)), 'd')
4748        ipar = numpy.zeros((parm), 'i')
4749        fpar = numpy.zeros((parm), 'd')
4750
4751        res = []
4752        for xscan in self.pitcon_par_space:
4753            Vtemp[:] = 0.0
4754            xr2[:] = 0.0
4755            sdot[:] = 0.0
4756            ipar[:] = 0
4757            fpar[:] = 0.0
4758
4759            iwork[0] = 0  # This is a startup
4760            iwork[1] = self.__settings__[
4761                "pitcon_init_par"
4762            ]  # Use X(1) for initial parameter
4763            iwork[2] = self.__settings__[
4764                "pitcon_par_opt"
4765            ]  # Parameterization option 0:allows program
4766            iwork[3] = self.__settings__[
4767                "pitcon_jac_upd"
4768            ]  # Update jacobian every newton step
4769            iwork[4] = self.__settings__[
4770                "pitcon_targ_val_idx"
4771            ]  # Seek target values for X(n)
4772            iwork[5] = self.__settings__[
4773                "pitcon_limit_point_idx"
4774            ]  # Seek limit points in X(n)
4775            iwork[6] = self.__settings__[
4776                "pitcon_output_lvl"
4777            ]  # Control amount of output.
4778            iwork[7] = 6  # Output unit 6=PC
4779            iwork[8] = self.__settings__[
4780                "pitcon_jac_opt"
4781            ]  # Jacobian choice. 0:supply jacobian,1:use forward difference,2:central difference
4782            iwork[9] = 0
4783            iwork[10] = 0
4784            iwork[11] = 0
4785            iwork[12] = 30
4786            iwork[13] = len(iwork)
4787            iwork[14] = 30 + (4 * parm)
4788            iwork[15] = len(rwork)
4789            iwork[16] = self.__settings__["pitcon_max_step"]  # max corrector steps
4790            iwork[17] = 0
4791            iwork[18] = 0
4792            iwork[19] = 0
4793            iwork[20] = 0
4794            iwork[21] = 0
4795            iwork[22] = 0
4796            iwork[23] = 0
4797            iwork[24] = 0
4798            iwork[25] = 0
4799            iwork[26] = 0
4800            iwork[27] = 0
4801
4802            rwork[0] = self.__settings__["pitcon_abs_tol"]  # Absolute error tolerance
4803            rwork[1] = self.__settings__["pitcon_rel_tol"]  # Relative error tolerance
4804            rwork[2] = self.__settings__["pitcon_min_step"]  # Minimum stepsize
4805            rwork[3] = self.__settings__["pitcon_max_step"]  # Maximum stepsize
4806            rwork[4] = self.__settings__["pitcon_start_step"]  # Starting stepsize
4807            rwork[5] = self.__settings__[
4808                "pitcon_start_dir"
4809            ]  # Starting direction +1.0/-1.0
4810            rwork[
4811                6
4812            ] = self.pitcon_targ_val  # Target value (Seek solution with iwork[4]=)
4813            rwork[7] = 0.0
4814            rwork[8] = 0.0
4815            rwork[9] = 0.0
4816            rwork[10] = 0.0
4817            rwork[11] = 0.0
4818            rwork[12] = 0.0
4819            rwork[13] = 0.0
4820            rwork[14] = 0.0
4821            rwork[15] = 0.0
4822            rwork[16] = 0.0
4823            rwork[17] = 0.0
4824            rwork[18] = 0.0
4825            rwork[19] = self.__settings__["pitcon_max_grow"]  # maximum growth factor
4826            rwork[20] = 0.0
4827            rwork[21] = 0.0
4828            rwork[22] = 0.0
4829            rwork[23] = 0.0
4830            rwork[24] = 0.0
4831            rwork[25] = 0.0
4832            rwork[26] = 0.0
4833            rwork[27] = 0.0
4834            rwork[28] = 0.0
4835
4836            setattr(self, scanpar, xscan)
4837            self.State()
4838
4839            if self.__HAS_COMPARTMENTS__ and self.__KeyWords__['Output_In_Conc']:
4840                ##  if self.__KeyWords__['Species_In_Conc']:
4841                self.__inspec__ = copy.copy(
4842                    self._SpeciesConcToAmount(self.state_species)
4843                )
4844            else:
4845                self.__inspec__ = copy.copy(self.state_species)
4846
4847            go = False
4848            if self.__StateOK__:
4849                go = True
4850            elif not self.__StateOK__ and self.__settings__["pitcon_allow_badstate"]:
4851                go = True
4852            else:
4853                go = False
4854
4855            if go:
4856                if self.__HAS_MOIETY_CONSERVATION__ == True:
4857                    temp = numpy.zeros((len(self.__SI__)), 'd')
4858                    # sI0_sim_init
4859                    for x in range(0, len(self.lzeromatrix_col)):
4860                        xr2[x] = self.__inspec__[self.nrmatrix_row[x]]
4861                else:
4862                    xr2[:-1] = copy.copy(self.state_species[:])
4863                xr2[-1] = copy.copy(xscan)
4864                for x in range(int(self.pitcon_iter)):
4865                    ierror, iwork, rwork, xr2 = pitcon.pitcon1(
4866                        fx, fpar, fx, ipar, iwork, rwork, xr2
4867                    )
4868                    if iwork[0] == 2:
4869                        if self.__settings__["pitcon_flux_gen"]:
4870                            if (
4871                                min(xr2[:-1]) < 0.0
4872                                and self.__settings__["pitcon_filter_neg"]
4873                            ):
4874                                pass
4875                            else:
4876                                xout = xr2.tolist()
4877                                a = xout.pop(-1)
4878                                if self.__HAS_MOIETY_CONSERVATION__ == True:
4879                                    xout = self.Fix_S_indinput(xout, amounts=True)
4880                                else:
4881                                    xout = numpy.array(xout)
4882                                if (
4883                                    self.__HAS_COMPARTMENTS__
4884                                    and self.__KeyWords__['Output_In_Conc']
4885                                ):
4886                                    xout = self._SpeciesAmountToConc(xout)
4887                                xout2 = (self.__FluxGen__(xout)).tolist()
4888                                xout = xout.tolist()
4889                                xout.insert(0, a)
4890                                if scanpar3d != None:
4891                                    xout.insert(0, scanpar3d)
4892                                xout2 = xout + xout2
4893                                res.append(xout2)
4894                        else:
4895                            if (
4896                                min(xr2[:-1]) < 0.0
4897                                and self.__settings__["pitcon_filter_neg"]
4898                            ):
4899                                pass
4900                            else:
4901                                xout = xr2.tolist()
4902                                a = xout.pop(-1)
4903                                if self.__HAS_MOIETY_CONSERVATION__ == True:
4904                                    xout = self.Fix_S_indinput(xout, amounts=True)
4905                                else:
4906                                    xout = numpy.array(xout)
4907                                if (
4908                                    self.__HAS_COMPARTMENTS__
4909                                    and self.__KeyWords__['Output_In_Conc']
4910                                ):
4911                                    xout = self._SpeciesAmountToConc(xout)
4912                                xout = xout.tolist()
4913                                xout.insert(0, a)
4914                                if scanpar3d != None:
4915                                    xout.insert(0, scanpar3d)
4916                                res.append(xout)
4917                    elif iwork[0] == 3:
4918                        print('\nTarget point:')
4919                        xout = xr2.tolist()
4920                        a = xout.pop(-1)
4921
4922                        if self.__HAS_MOIETY_CONSERVATION__ == True:
4923                            xout = self.Fix_S_indinput(xout, amounts=True)
4924                        else:
4925                            xout = numpy.array(xout)
4926                        if (
4927                            self.__HAS_COMPARTMENTS__
4928                            and self.__KeyWords__['Output_In_Conc']
4929                        ):
4930                            xout = self._SpeciesAmountToConc(xout)
4931                        xout = xout.tolist()
4932                        xout.insert(0, a)
4933
4934                        print(xout)
4935                        self.pitcon_target_points.append(xout)
4936                    elif iwork[0] == 4:
4937                        print('\nLimit point')
4938                        xout = xr2.tolist()
4939                        a = xout.pop(-1)
4940
4941                        if self.__HAS_MOIETY_CONSERVATION__ == True:
4942                            xout = self.Fix_S_indinput(xout, amounts=True)
4943                        else:
4944                            xout = numpy.array(xout)
4945                        if (
4946                            self.__HAS_COMPARTMENTS__
4947                            and self.__KeyWords__['Output_In_Conc']
4948                        ):
4949                            xout = self._SpeciesAmountToConc(xout)
4950                        xout = xout.tolist()
4951                        xout.insert(0, a)
4952
4953                        print(xout)
4954                        self.pitcon_limit_points.append(xout)
4955                    elif iwork[0] == 1:
4956                        pass
4957                    else:
4958                        print(iwork[0])
4959                        # raw_input()
4960            else:
4961                print('\nInvalid steady state, skipping ...')
4962
4963        if self.__settings__["pitcon_filter_neg_res"]:
4964            for result in range(len(res) - 1, -1, -1):
4965                if min(res[result][: len(self.species) + 1]) < 0.0:
4966                    res.pop(result)
4967        setattr(self, scanpar, par_hold)
4968        return numpy.array(res)
4969
4970    def Simulate(self, userinit=0):
4971        """
4972        PySCeS integration driver routine that evolves the system over the time.
4973        Resulting array of species concentrations is stored in the **mod.data_sim** object
4974        Initial concentrations can be selected using *mod.__settings__['mode_sim_init']*
4975        (default=0):
4976
4977        - 0  initialise with intial concentrations
4978        - 1  initialise with a very small (close to zero) value
4979        - 2  initialise with results of previously calculated steady state
4980        - 3  initialise with final point of previous simulation
4981
4982        *userinit* values can be (default=0):
4983
4984        - 0: initial species concentrations intitialised from (mod.S_init),
4985             time array calculated from sim_start/sim_end/sim_points
4986        - 1: intial species concentrations intitialised from (mod.S_init) existing
4987             "mod.sim_time" used directly
4988        - 2: initial species concentrations read from "mod.__inspec__",
4989             "mod.sim_time" used directly
4990        """
4991        # check if stoichiometry has been adjusted using Stoich_nmatrix_SetValue
4992        # - brett 20050719
4993        if not self.__StoichOK:
4994            self.Stoichiometry_ReAnalyse()
4995
4996        # check for zero first point in user-supplied mod.sim_time, add if needed
4997        self._sim_time_bak = None
4998        if userinit != 0:
4999            if self.sim_time[0] != 0:
5000                self._sim_time_bak = copy.copy(self.sim_time)
5001                self.sim_time = [0.0] + list(self._sim_time_bak)
5002
5003        # initialises self.__inspec__[x] with self.sXi
5004        if userinit == 1:
5005            eval(self.__mapFunc_R__)
5006        elif userinit == 2:
5007            try:
5008                assert len(self.__inspec__) == len(self.__species__)
5009            except:
5010                print(
5011                    '\nINFO: mod.__inspec__ is the incorrect length, initialising with .sX_init'
5012                )
5013                self.__inspec__ = numpy.zeros(len(self.__species__))
5014                eval(self.__mapFunc_R__)
5015        else:
5016            self.sim_start = float(self.sim_start)
5017            self.sim_end = float(self.sim_end)
5018            self.sim_points = int(self.sim_points)
5019            if self.sim_points == 1.0:
5020                print(
5021                    '*****\nWARNING: simulations require a minimum of 2 points,\
5022setting sim_points = 2.0\n*****'
5023                )
5024                self.sim_points = 2.0
5025            self.sim_time = numpy.linspace(
5026                self.sim_start, self.sim_end, self.sim_points, endpoint=True, retstep=False
5027            )
5028            eval(self.__mapFunc_R__)
5029
5030        # initialise __rrule__ to mod.<rule>_init
5031        if self.__HAS_RATE_RULES__:
5032            for r in range(len(self.__rate_rules__)):
5033                self.__rrule__[r] = getattr(
5034                    self, '{}_init'.format(self.__rate_rules__[r])
5035                )
5036        for c in range(len(self.__CsizeAllIdx__)):
5037            cval = getattr(self, '{}_init'.format(self.__CsizeAllIdx__[c]))
5038            setattr(self, self.__CsizeAllIdx__[c], cval)
5039            self.__CsizeAll__[c] = cval
5040        # set initialisation array to amounts
5041        if self.__HAS_COMPARTMENTS__ and self.__KeyWords__['Species_In_Conc']:
5042            self.__inspec__ = self._SpeciesConcToAmount(self.__inspec__)
5043
5044        # set mod.<species> to corrected mod.<species>_init value
5045        for s in range(len(self.__species__)):
5046            setattr(self, self.__species__[s], self.__inspec__[s])
5047
5048        # This should work ok __Build_Tvec__ uses .self.__inspec__ to create Tvec
5049        if self.__HAS_MOIETY_CONSERVATION__ == True:
5050            self.__Build_Tvec__(amounts=True)
5051            self.showConserved(screenwrite=0)
5052            if self.__settings__['display_debug'] == 1:
5053                print(self.conserved_sums)
5054
5055        # Initialise the simulation ...
5056        if self.__settings__['mode_sim_init'] == 0:
5057            # Start with s[x] at their initial values
5058            s0_sim_init = copy.copy(self.__inspec__)
5059        elif self.__settings__['mode_sim_init'] == 1:
5060            # Start with s[x] at zero ... well close to it anyway
5061            s0_sim_init = copy.copy(self.__inspec__)
5062            s0_sim_init[:] = self.__settings__['small_concentration']
5063        elif self.__settings__['mode_sim_init'] == 2:
5064            # Start with s[x] at the previous steady state if it exists and check if s[x] < 0.0
5065            # if so set that value to 1.0e-10
5066            if self.state_species != None:
5067                s0_sim_init = copy.copy(self.state_species) * 1.0
5068                for x in range(len(s0_sim_init)):
5069                    if s0_sim_init[x] < 0.0:
5070                        s0_sim_init[x] = self.__settings__['small_concentration']
5071                        print(
5072                            'Negative concentration detected in SimInit: s['
5073                            + repr(x)
5074                            + '] set to '
5075                            + repr(self.__settings__['small_concentration'])
5076                        )
5077            else:
5078                s0_sim_init = copy.copy(self.__inspec__)
5079        elif self.__settings__['mode_sim_init'] == 3:
5080            # Start with s[x] at the final point of the previous simulation if exists -- johann 20050220
5081            try:
5082                s0_sim_init = copy.copy(self.data_sim.species[-1])
5083            except:
5084                s0_sim_init = copy.copy(self.__inspec__)
5085        else:
5086            s0_sim_init = copy.copy(self.__inspec__)
5087
5088        # print 'Sim debug'
5089        # print s0_sim_init
5090        if self.__HAS_MOIETY_CONSERVATION__ == True:
5091            temp = numpy.zeros((len(self.__SI__)), 'd')
5092            for x in range(0, len(self.lzeromatrix_col)):
5093                temp[x] = s0_sim_init[self.nrmatrix_row[x]]
5094            s0_sim_init = temp
5095            del temp
5096        # print s0_sim_init
5097
5098        # ok so now we add RateRules to the initialisation_vec and see what happens
5099        if self.__HAS_RATE_RULES__:
5100            s0_sim_init = numpy.concatenate([s0_sim_init, self.__rrule__])
5101
5102        # re-set self._sim recarray (otherwise self.sim is not updated)
5103        self._sim = None
5104
5105        # real pluggable integration routines - brett 2007
5106        # the array copy is important ... brett leave it alone!
5107        Tsim0 = time.time()
5108        if self.mode_integrator == 'LSODA':
5109            sim_res, rates, simOK = self.LSODA(copy.copy(s0_sim_init))
5110        elif self.mode_integrator == 'CVODE':
5111            sim_res, rates, simOK = self.CVODE(copy.copy(s0_sim_init))
5112        # remove zero point from reported simulation data if necessary
5113        if self._sim_time_bak is not None:
5114            self.sim_time = copy.copy(self._sim_time_bak)
5115            sim_res = sim_res[1:]
5116            rates = rates[1:]
5117        Tsim1 = time.time()
5118        if self.__settings__['lsoda_mesg']:
5119            print(
5120                "{} time for {} points: {}".format(
5121                    self.mode_integrator, len(self.sim_time), Tsim1 - Tsim0
5122                )
5123            )
5124
5125        if self.__HAS_RATE_RULES__:
5126            sim_res, rrules = numpy.split(sim_res, [len(self.__species__)], axis=1)
5127            print('RateRules evaluated and added to mod.data_sim.')
5128
5129        self.data_sim = IntegrationDataObj()
5130        self.IS_VALID = simOK
5131        self.data_sim.setTime(self.sim_time)
5132        self.data_sim.setSpecies(sim_res, self.__species__)
5133        self.data_sim.setRates(rates, self.__reactions__)
5134        if self.__HAS_RATE_RULES__:
5135            self.data_sim.setRules(rrules, self.__rate_rules__)
5136        if len(self.CVODE_extra_output) > 0:
5137            self.data_sim.setXData(self.CVODE_xdata, lbls=self.CVODE_extra_output)
5138            self.CVODE_xdata = None
5139        if not simOK:
5140            print('Simulation failure')
5141        del sim_res
5142
5143    @property
5144    def sim(self):
5145        if self._sim is None and self.data_sim is not None:
5146            data = self.data_sim.getAllSimData(lbls=True)
5147            self._sim = numpy.rec.fromrecords(data[0], names=data[1])
5148        return self._sim
5149
5150    def State(self):
5151        """
5152        State()
5153
5154        PySCeS non-linear solver driver routine. Solve for a steady state using HYBRD/NLEQ2/FINTSLV
5155        algorithms. Results are stored in mod.state_species and mod.state_flux. The results
5156        of a steady-state analysis can be viewed with the mod.showState() method.
5157
5158        The solver can be initialised in 3 ways using the mode_state_init switch.
5159        mod.mode_state_init = 0 initialize with species initial values
5160        mod.mode_state_init = 1 initialize with small values
5161        mod.mode_state_init = 2 initialize with the final value of a 10-logstep simulation numpy.logspace(0,5,18)
5162
5163        Arguments:
5164        None
5165
5166        """
5167        # check if the stoichiometry has been adjusted using Stoich_nmatrix_SetValue - brett 20050719
5168        if not self.__StoichOK:
5169            self.Stoichiometry_ReAnalyse()
5170
5171        self.__StateOK__ = True
5172
5173        # function to feed the simulation routine if used to initialise the solver
5174        Vtemp = numpy.zeros((self.__Nshape__[1]), 'd')
5175
5176        def function_sim(s, t):
5177            return self._EvalODE(s, Vtemp)
5178
5179        # set self.__inspec__ to current Xi values
5180        eval(self.__mapFunc_R__)
5181
5182        # initialise __rrule__ to mod.<rule>_init
5183        if self.__HAS_RATE_RULES__:
5184            for r in range(len(self.__rate_rules__)):
5185                self.__rrule__[r] = getattr(
5186                    self, '{}_init'.format(self.__rate_rules__[r])
5187                )
5188        # set compartment values to initial values
5189        for c in range(len(self.__CsizeAllIdx__)):
5190            cval = getattr(self, '{}_init'.format(self.__CsizeAllIdx__[c]))
5191            setattr(self, self.__CsizeAllIdx__[c], cval)
5192            self.__CsizeAll__[c] = cval
5193        # set initialisation array to amounts
5194        if self.__HAS_COMPARTMENTS__ and self.__KeyWords__['Species_In_Conc']:
5195            self.__inspec__ = self._SpeciesConcToAmount(self.__inspec__)
5196        # set mod.<species> to corrected mod.<species>_init value
5197        for s in range(len(self.__species__)):
5198            setattr(self, self.__species__[s], self.__inspec__[s])
5199        # This should work ok __Build_Tvec__ uses .self.__inspec__ to create Tvec
5200        if self.__HAS_MOIETY_CONSERVATION__ == True:
5201            self.__Build_Tvec__(amounts=True)
5202            self.showConserved(screenwrite=0)
5203            if self.__settings__['display_debug'] == 1:
5204                print(self.conserved_sums)
5205
5206        # clear the solver initialisation array
5207        s0_ss_init = None
5208        # save the __settings__['hybrd_factor']
5209        hybrd_factor_temp = copy.copy(self.__settings__['hybrd_factor'])
5210
5211        # use StateInit to initialise the solver with either 0:initval 1:zeroval 2:sim 3:1%state
5212        # in all cases fallback to init
5213        # initialising to previous ss seems problematic so I use |10%| of previous - brett
5214        if self.mode_state_init == 0:
5215            # Use initial S values
5216            s0_ss_init = self.__inspec__.copy()
5217        elif self.mode_state_init == 1:
5218            # Use almost zero values 1.0e-8 any smaller seems to cause a problem
5219            # this is user defineable option --> model.ZeroVal - brett 20040121
5220            s0_ss_init = self.__inspec__.copy()
5221            s0_ss_init[:] = self.__settings__['small_concentration']
5222        elif self.mode_state_init == 2:
5223            print('This initialisation mode has been disabled, using initial values.')
5224            s0_ss_init = self.__inspec__.copy()
5225        ##  # Perform a 10-logstep simulation numpy.logspace(0,5,18) and initialise solver with final value
5226        ##  # This is guesstimate ... if anyone has a better idea please let me know
5227        ##  # it should and be a user defineable array (min/max/len)- brett 20031215
5228        ##  self.__settings__['hybrd_factor'] = 10
5229        ##  try:
5230        ##  s0_ss_init = scipy.integrate.odeint(function_sim,self.__inspec__.tolist(),self.__mode_state_init2_array__,10000,full_output=0)
5231        ##  if self.__HAS_MOIETY_CONSERVATION__ == True:
5232        ##  s0_ss_init = self.Fix_S_fullinput(s0_ss_init[-1], amounts=True)
5233        ##  else:
5234        ##  s0_ss_init = s0_ss_init[-1]
5235        ##  except:
5236        ##  s0_ss_init = copy.copy(self.__inspec__)
5237        elif self.mode_state_init == 3:
5238            # Use |10%| of previous steady-state - seems to be safe, needs more testing and probably professional help
5239            # My rationale for this is "a set of approximate values where all variables are relatively close"
5240            # without having the overhead of a simulation ...
5241            # will eventually be a user option so that it can be +/- x% previous steadystate - brett 20031215
5242            # Note: hybrid doesn't seem to like to start too close to its solution more than 10% is dodgy
5243            if (
5244                self.state_species != None
5245                and self.__HAS_COMPARTMENTS__
5246                and self.__KeyWords__['Output_In_Conc']
5247            ):
5248                s0_ss_init = self._SpeciesConcToAmount(
5249                    abs(copy.copy(self.state_species))
5250                    * float(self.__settings__['mode_state_init3_factor'])
5251                )
5252            else:
5253                s0_ss_init = copy.copy(self.__inspec__)
5254        else:
5255            s0_ss_init = copy.copy(self.__inspec__)
5256        # reset __settings__['hybrd_factor']
5257        self.__settings__['hybrd_factor'] = hybrd_factor_temp
5258
5259        # print 'State debug'
5260        # print s0_ss_init
5261        # form an initialisation vector of independent species
5262        if self.__HAS_MOIETY_CONSERVATION__ == True:
5263            temp = numpy.zeros((len(self.__SI__)), 'd')
5264            for x in range(0, len(self.lzeromatrix_col)):
5265                temp[x] = s0_ss_init[self.nrmatrix_row[x]]
5266            # add RateRules to the initialisation_vec and see what happens
5267            if self.__HAS_RATE_RULES__:
5268                temp = numpy.concatenate([temp, self.__rrule__])
5269            s0_ss_init = temp
5270            del temp
5271        # print s0_ss_init
5272
5273        available_solvers = ['HYBRD']
5274
5275        # set mode_solver to new syntax for backwards compatibility only
5276        if self.mode_solver == 0:
5277            self.mode_solver = 'HYBRD'
5278        elif self.mode_solver == 1:
5279            self.mode_solver = 'FINTSLV'
5280        elif self.mode_solver == 2:
5281            self.mode_solver = 'NLEQ2'
5282
5283        # check for nleq2 and add if available this should be first
5284        if nleq2_switch == 1:
5285            available_solvers.append('NLEQ2')
5286        else:
5287            if self.mode_solver == 'NLEQ2':
5288                self.mode_solver = 'HYBRD'
5289            print(
5290                'INFO: switching to HYBRD.\nNleq2 solver not available see /nleq/readme.txt for details'
5291            )
5292
5293        # ******* OTHER SOLVERS GO IN HERE *******
5294
5295        # ******* OTHER SOLVERS GO IN HERE *******
5296
5297        # shall we add HYBRD to fallback? this should be last
5298        if self.__settings__['mode_solver_fallback_integration']:
5299            available_solvers.append('FINTSLV')
5300
5301        if not self.mode_solver_fallback == 1:
5302            assert (
5303                self.mode_solver in available_solvers
5304            ), '\nERROR: {} is not a valid ({}) solver!'.format(
5305                solver, str(available_solvers)
5306            )
5307            available_solvers = [self.mode_solver]
5308
5309        # if solver other than HYBRD is selected move it to front of list so it is run first
5310        if self.mode_solver != 'HYBRD':
5311            available_solvers.insert(
5312                0, available_solvers.pop(available_solvers.index(self.mode_solver))
5313            )
5314
5315        STATE_XOUT = False
5316        STATE_xdata = None
5317        if len(self.STATE_extra_output) > 0:
5318            out = []
5319            for d in self.STATE_extra_output:
5320                if (
5321                    hasattr(self, d)
5322                    and d
5323                    not in self.__species__ + self.__reactions__ + self.__rate_rules__
5324                ):
5325                    out.append(d)
5326                else:
5327                    print(
5328                        '\nWARNING: STATE is ignoring extra data ({}), it either doesn\'t exist or it\'s a species, rate or rule.\n'.format(
5329                            d
5330                        )
5331                    )
5332            if len(out) > 0:
5333                self.STATE_extra_output = out
5334                STATE_XOUT = True
5335            del out
5336
5337        self.__StateOK__ = True
5338        state_species = None
5339        rrules = None
5340        state_flux = None
5341        for solver in available_solvers:
5342            if solver == 'HYBRD':
5343                state_species, self.__StateOK__ = self.HYBRD(s0_ss_init.copy())
5344            elif solver == 'NLEQ2':
5345                state_species, self.__StateOK__ = self.NLEQ2(s0_ss_init.copy())
5346            elif solver == 'FINTSLV':
5347                state_species, self.__StateOK__ = self.FINTSLV(s0_ss_init.copy())
5348            # In case of a number (scalar) output from the solver
5349            if numpy.isscalar(state_species):
5350                state_species = numpy.array([state_species], 'd')
5351
5352            # this gets everything into the current tout state
5353            tmp = self._EvalODE(state_species.copy(), Vtemp)
5354            state_flux = self.__vvec__
5355
5356            if self.__HAS_RATE_RULES__:
5357                state_species, rrules = numpy.split(
5358                    state_species, [self.Nrmatrix.shape[0]]
5359                )
5360            # test for negative concentrations
5361            if (state_species < 0.0).any():
5362                self.__StateOK__ = False
5363                if self.__settings__['mode_state_mesg']:
5364                    print('WARNING!! Negative concentrations detected.')
5365            if self.__StateOK__:
5366                break
5367            else:
5368                if (
5369                    self.mode_solver_fallback
5370                    and self.__settings__['solver_switch_warning']
5371                ):
5372                    slv_idx = available_solvers.index(solver)
5373                    if slv_idx != len(available_solvers) - 1:
5374                        print(
5375                            'INFO: STATE is switching to {} solver.'.format(
5376                                available_solvers[slv_idx + 1]
5377                            )
5378                        )
5379                    else:
5380                        print('INFO: STATE calculation failed!')
5381        if STATE_XOUT:
5382            STATE_xdata = self._EvalExtraData(self.STATE_extra_output)
5383
5384        # the current status quo is all state algorithms will use and produce results
5385        # in amounts and autoconversion to and from species takes place in the calling
5386        # algorithms -- brett2008
5387        # THIS IS OPPOSITE TO THE SIMULATE METHOD brett - again
5388
5389        if self.__HAS_MOIETY_CONSERVATION__ == True:
5390            state_species = self.Fix_S_indinput(state_species, amounts=True)
5391        if self.__HAS_COMPARTMENTS__ and self.__KeyWords__['Output_In_Conc']:
5392            self.state_species = self._SpeciesAmountToConc(state_species.copy())
5393        else:
5394            self.state_species = state_species
5395
5396        self.state_flux = state_flux
5397
5398        del state_species, state_flux
5399
5400        # final check for a bad state set check if fluxes are == mach_eps
5401        # this is almost never going to be true
5402        if (self.state_flux == self.__settings__['mach_floateps']).any():
5403            print('\nWARNING: extremely small flux detected! proceed with caution:')
5404            print(self.state_flux)
5405            ##  self.__StateOK__ = False
5406
5407        if not self.__StateOK__:
5408            print(
5409                '\n***\nWARNING: invalid steady state solution (species concentrations and fluxes)\n***\n'
5410            )
5411            if self.__settings__['mode_state_nan_on_fail']:
5412                self.state_species[:] = numpy.NaN
5413                self.state_flux[:] = numpy.NaN
5414
5415        # set the instance steady state flux and species attributes
5416        self.SetStateSymb(self.state_flux, self.state_species)
5417
5418        # coming soon to a terminal near you
5419        self.data_sstate = StateDataObj()
5420        self.data_sstate.setSpecies(self.state_species, self.__species__)
5421        self.data_sstate.setFluxes(self.state_flux, self.__reactions__)
5422        if self.__HAS_RATE_RULES__:
5423            self.data_sstate.setRules(rrules, self.__rate_rules__)
5424        if STATE_XOUT:
5425            self.data_sstate.setXData(STATE_xdata, lbls=self.STATE_extra_output)
5426            del STATE_xdata
5427
5428        self.data_sstate.IS_VALID = self.__StateOK__
5429
5430        if self.__settings__['display_debug'] == 1:
5431            print('self.state_species')
5432            print(self.state_species)
5433            print('self.state_flux')
5434            print(self.state_flux)
5435
5436    # driver routines that support the core routines
5437    #   core support
5438
5439    # takes the flux and species arrays and
5440    # assigns them as instances variable self.Jx and self.Xss
5441    # I'm not sure if anyone wants to use this in real life so it might be hidden at some point - brett 20040122
5442    # gone - brett 20040506 ... back brett 20040720
5443    def SetStateSymb(self, flux, metab):
5444        """
5445        SetStateSymb(flux,metab)
5446
5447        Sets the individual steady-state flux and concentration attributes as
5448        mod.J_<reaction> and mod.<species>_ss
5449
5450        Arguments:
5451
5452        flux: the steady-state flux array
5453        metab: the steady-state concentration array
5454
5455        """
5456        for x in range(0, len(self.state_species)):
5457            setattr(self, self.__species__[x] + '_ss', metab[x])
5458
5459        for x in range(0, len(self.state_flux)):
5460            setattr(self, 'J_' + self.__reactions__[x], flux[x])
5461
5462    # uses __inspec__ to build Tvec
5463    def __Build_Tvec__(self, amounts=True):
5464        """
5465        __Build_Tvec_(concs=False)
5466
5467        Creates vectors of conserved moiety totals from __inspec__
5468        self.__tvec_a__ = amounts
5469        self.__tvec_c__ = concentrations
5470
5471        Arguments:
5472        None
5473
5474        """
5475        if self.__HAS_MOIETY_CONSERVATION__ == True:
5476            if amounts:
5477                self.__tvec_a__ = numpy.add.reduce(
5478                    copy.copy(self.__reordered_lcons) * self.__inspec__, 1
5479                )
5480                self.__tvec_c__ = numpy.add.reduce(
5481                    copy.copy(self.__reordered_lcons)
5482                    * self._SpeciesAmountToConc(self.__inspec__),
5483                    1,
5484                )
5485            else:
5486                self.__tvec_c__ = numpy.add.reduce(
5487                    copy.copy(self.__reordered_lcons) * self.__inspec__, 1
5488                )
5489                self.__tvec_a__ = numpy.add.reduce(
5490                    copy.copy(self.__reordered_lcons)
5491                    * self._SpeciesConcToAmount(self.__inspec__),
5492                    1,
5493                )
5494
5495    def showConserved(self, File=None, screenwrite=1, fmt='%2.3f'):
5496        """
5497        showConserved(File=None,screenwrite=1,fmt='%2.3f')
5498
5499        Print the moiety conserved cycles present in the system.
5500
5501        Arguments:
5502
5503        File [default=None]: an open writable Python file object
5504        screenwrite [default=1]: write results to console (0 means no reponse)
5505        fmt [default='%2.3f']: the output number format string
5506
5507        """
5508        if self.__HAS_MOIETY_CONSERVATION__ == True:
5509            Tlist = list(range(0, len(self.__tvec_a__)))
5510            if Tlist != []:
5511                ConSumPstr = ''
5512                for x in range(0, len(Tlist)):
5513                    for y in range(0, len(self.conservation_matrix_col)):
5514                        if self.conservation_matrix[Tlist[x], y] > 0.0:
5515                            # coeff = self.__settings__['mode_number_format'] % (s_init[y]*abs(conservation_matrix[Tlist[x],y]))
5516                            coeff = fmt % abs(self.conservation_matrix[Tlist[x], y])
5517                            met = self._D_s_Order[self.conservation_matrix_col[y]]
5518                            ConSumPstr += (
5519                                ' + {' + coeff + '}' + met.replace('self.', '')
5520                            )
5521                        elif self.conservation_matrix[Tlist[x], y] < 0.0:
5522                            # coeff = self.__settings__['mode_number_format'] % (s_init[y]*abs(conservation_matrix[Tlist[x],y]))
5523                            coeff = fmt % abs(self.conservation_matrix[Tlist[x], y])
5524                            met = self._D_s_Order[self.conservation_matrix_col[y]]
5525                            ConSumPstr += (
5526                                ' - {' + coeff + '}' + met.replace('self.', '')
5527                            )
5528                    if (
5529                        self.__HAS_COMPARTMENTS__
5530                        and self.__KeyWords__['Output_In_Conc']
5531                    ):
5532                        ConSumPstr += (
5533                            ' = '
5534                            + self.__settings__['mode_number_format']
5535                            % self.__tvec_c__[Tlist[x]]
5536                            + '\n'
5537                        )
5538                    else:
5539                        ConSumPstr += (
5540                            ' = '
5541                            + self.__settings__['mode_number_format']
5542                            % self.__tvec_a__[Tlist[x]]
5543                            + '\n'
5544                        )
5545            self.conserved_sums = ConSumPstr
5546        else:
5547            self.conserved_sums = 'No moiety conservation'
5548
5549        if File != None:
5550            print('\nConserved relationships')
5551            # assert type(File) == file, 'showConserved() needs an open file object'
5552            File.write('\n## Conserved relationships\n')
5553            File.write(self.conserved_sums)
5554        elif screenwrite:
5555            print('\nConserved relationships')
5556            print(self.conserved_sums)
5557
5558    def showFluxRelationships(self, File=None):
5559        """
5560        showConserved(File=None)
5561
5562        Print the flux relationships present in the system.
5563
5564        Arguments:
5565
5566        File [default=None]: an open writable Python file object
5567
5568        """
5569        Ostr = ''
5570        for row in range(self.__kzeromatrix__.shape[0]):
5571            Ostr += "{} =".format(self.reactions[self.kzeromatrix_row[row]])
5572            for col in range(self.__kzeromatrix__.shape[1]):
5573                if self.__kzeromatrix__[row, col] != 0.0:
5574                    if self.__kzeromatrix__[row, col] > 0.0:
5575                        Ostr += " + {%2.2f}%s" % (
5576                            abs(self.__kzeromatrix__[row, col]),
5577                            self.reactions[self.kzeromatrix_col[col]],
5578                        )
5579                    else:
5580                        Ostr += " - {%2.2f}%s" % (
5581                            abs(self.__kzeromatrix__[row, col]),
5582                            self.reactions[self.kzeromatrix_col[col]],
5583                        )
5584            Ostr += '\n'
5585
5586        if File != None:
5587            print('\nFlux relationships')
5588            # assert type(File) == file, 'showConserved() needs an open file object'
5589            File.write('\n## Flux relationships\n')
5590            File.write(Ostr)
5591        else:
5592            print('\nFlux relationships')
5593            print(Ostr)
5594
5595    # Calculate dependant variables done directly in EvalREq2 this is a utility version
5596    def Fix_S_fullinput(self, s_vec, amounts=True):
5597        """
5598        Fix_S_fullinput(s_vec)
5599
5600        Using the full concentration vector evaluate the dependent species
5601
5602        Arguments:
5603
5604        s_vec: a full length concentration vector
5605
5606        """
5607        # s_vec = copy.copy(s)
5608        for x in range(0, len(self.lzeromatrix_col)):
5609            self.__SI__[x] = s_vec[self.lzeromatrix_col[x]]
5610
5611        for x in range(0, len(self.lzeromatrix_row)):
5612            if amounts:
5613                s_vec[self.lzeromatrix_row[x]] = self.__tvec_a__[x] + numpy.add.reduce(
5614                    self.__lzeromatrix__[x, :] * self.__SI__
5615                )  # there might be a way to reduce the 2 for loops
5616            else:
5617                s_vec[self.lzeromatrix_row[x]] = self.__tvec_c__[x] + numpy.add.reduce(
5618                    self.__lzeromatrix__[x, :] * self.__SI__
5619                )  # there might be a way to reduce the 2 for loops
5620
5621        return s_vec
5622
5623    # Calculate dependant variables done directly in EvalREq2B this is a utility version
5624    def Fix_S_indinput(self, s_vec, amounts=True):
5625        """
5626        Fix_S_indinput(s_vec, amounts=True)
5627        whether to use self.__tvec_a__ (default)
5628        or self.__tvec_c__
5629
5630        Given a vector of independent species evaluate and return a full concentration vector.
5631
5632        Arguments:
5633
5634        s_vec: vector of independent species
5635
5636        """
5637        # stick SI into s using Nrrow order
5638        for x in range(len(s_vec)):
5639            self.__SALL__[self.nrmatrix_row[x]] = s_vec[x]
5640        # stick SD into s using lzerorow order
5641        for x in range(len(self.lzeromatrix_row)):
5642            if amounts:
5643                self.__SALL__[self.lzeromatrix_row[x]] = self.__tvec_a__[
5644                    x
5645                ] + numpy.add.reduce(self.__lzeromatrix__[x, :] * s_vec)
5646            else:
5647                self.__SALL__[self.lzeromatrix_row[x]] = self.__tvec_c__[
5648                    x
5649                ] + numpy.add.reduce(self.__lzeromatrix__[x, :] * s_vec)
5650        return copy.copy(self.__SALL__)
5651
5652    #   output support routines
5653
5654    # Quick and dirty flux regeneration uses the Rx form of the RE's to cater for potential conservation
5655    # caters for both vectors and arrays and is conservation aware
5656    def __FluxGen__(self, s):
5657        """
5658        **Deprecating** only used by **PITCON**
5659
5660        s: species vector/array
5661        """
5662        Vtemp = numpy.zeros((self.__Nshape__[1]), 'd')
5663        try:
5664            s.shape[0]
5665            Vout = numpy.zeros((len(s), len(self.__vvec__)), 'd')
5666            for x in range(len(s)):
5667                if (
5668                    self.__HAS_MOIETY_CONSERVATION__ == True
5669                ):  # depending if there is conservation -- N.L_switch is: 0 for none or 1 for present
5670                    Vout[x, :] = self._EvalREq2_alt(s[x, :], Vtemp)
5671                elif self.__HAS_MOIETY_CONSERVATION__ == False:
5672                    Vout[x, :] = self._EvalREq(s[x, :], Vtemp)
5673        except:
5674            if (
5675                self.__HAS_MOIETY_CONSERVATION__ == True
5676            ):  # depending if there is conservation -- N.L_switch is: 0 for none or 1 for present
5677                Vout = self._EvalREq2_alt(s, Vtemp)
5678            elif self.__HAS_MOIETY_CONSERVATION__ == False:
5679                Vout = self._EvalREq(s, Vtemp)
5680        return Vout
5681
5682    def FluxGenSim(self, s):
5683        """
5684        **Deprecated**
5685        """
5686        pass
5687
5688    def ParGenSim(self):
5689        """
5690        **Deprecated**
5691        """
5692        pass
5693
5694    def Fix_Sim(self, metab, flux=0, par=0):
5695        """
5696        **Deprecated**
5697        """
5698        pass
5699
5700    # MCA routines
5701    #   elasticity routines
5702
5703    def EvalEvar(self, input=None, input2=None):
5704        """
5705        EvalEvar(input=None,input2=None)
5706
5707        Calculate reaction elasticities towards the variable species.
5708
5709        Both inputs (input1=species,input2=rates) should be valid (steady state for MCA) solutions and given in the correct order for them to be used. If either or both are missing the last state values are used automatically.
5710        Elasticities are scaled using input 1 and 2.
5711
5712        Arguments::
5713
5714         - input [default=None]:  species concentration vector
5715         - input2 [default=None]: reaction rate vector
5716
5717        Settings, set in mod.__settings__::
5718
5719         - elas_evar_upsymb   [default = 1] attach individual elasticity symbols to model instance
5720         - elas_zero_conc_fix [default=False] if zero concentrations are detected in a steady-state solution make it a very small number
5721         - elas_zero_flux_fix [default=False] if zero fluxes are detected in a steady-state solution make it a very small number
5722         - elas_scaling_div0_fix [default=False] if INf's are detected after scaling set to zero
5723
5724        """
5725        if input == None or input2 == None:
5726            input = self.state_species
5727            input2 = self.state_flux
5728            # print 'INFO: Using state_species and state_flux as input'
5729        else:
5730            assert len(input) == len(self.species), (
5731                'length error this array must have '
5732                + str(len(self.species))
5733                + ' elements'
5734            )
5735            assert len(input2) == len(self.reactions), (
5736                'length error this array must have '
5737                + str(len(self.reactions))
5738                + ' elements'
5739            )
5740            # not really necessary but in here just in case - brett 20040930
5741            # map input back to self.Sx
5742            exec(self.__CDvarUpString__)
5743
5744        if self.__settings__['elas_zero_flux_fix']:
5745            val = self.__settings__['mach_floateps'] ** 2
5746            for x in range(len(input2)):
5747                if abs(input2[x]) <= val:
5748                    print(
5749                        'Info: zero flux detected: J_{} set to {}'.format(
5750                            self.reactions[x], val
5751                        )
5752                    )
5753                    input2[x] = val
5754        if self.__settings__['elas_zero_conc_fix']:
5755            val = self.__settings__['mach_floateps'] ** 2
5756            for x in range(len(input)):
5757                if abs(input[x]) <= val:
5758                    print(
5759                        'Info: zero concentration detected: {} set to {}'.format(
5760                            self.species[x], val
5761                        )
5762                    )
5763                    input[x] = val
5764
5765        if self.__settings__['display_debug'] == 1:
5766            print('\nVarinput = ' + repr(input) + '\n')
5767
5768        self.__evmatrix__ = numpy.zeros(
5769            (len(self.__reactions__), len(self.__species__)), 'd'
5770        )
5771
5772        # scale KL (future refactoring allow user input)
5773        self.ScaleKL(input, input2)
5774
5775        # create tuples of the rows and columns for the Ev matrix
5776        self.elas_var_row = tuple(copy.copy(self.__reactions__))
5777        col = copy.copy(self._D_s_Order)
5778        for x in range(len(self._D_s_Order)):
5779            col[x] = col[x].replace('self.', '')
5780        self.elas_var_col = tuple(col)
5781        del col
5782
5783        # attempt to evaluate every variable against every flux: E's > mach_eps are assumed to exist
5784        for react in range(len(self.__reactions__)):
5785            if self.__settings__['display_debug'] == 1:
5786                print('\nReaction: ' + self.__reactions__[react])
5787            for met in range(len(self.__species__)):
5788                countV = 0
5789                countMet = 0
5790                # this modification should make this independant of a steady state - finally implimented july2004
5791                # eval('self.'+ self.__species__[met] + '_ss'),order=self.__settings__['mode_elas_deriv_order'],dx=hstep,n=1,\
5792                try:
5793                    """
5794                    I borrowed the idea of scaling the stepsize to So from Herbert Sauro's Jarnac TModel.uEEOp function
5795                    brett - 2004-08-18
5796                    """
5797
5798                    hstep = (
5799                        input[met] * self.__settings__['mode_elas_deriv_factor']
5800                    )  # self.__settings__['mode_elas_deriv_factor'] = 0.0001
5801                    if (
5802                        abs(hstep) < self.__settings__['mode_elas_deriv_min']
5803                    ):  # self.__settings__['mode_elas_deriv_min'] = 1.0e-12
5804                        hstep = self.__settings__['mode_elas_deriv_min']
5805
5806                    a = ScipyDerivative(
5807                        self.__num_deriv_function__,
5808                        input[met],
5809                        order=self.__settings__['mode_elas_deriv_order'],
5810                        dx=hstep,
5811                        n=1,
5812                        args=(self.__reactions__[react], self.__species__[met]),
5813                    )
5814                except Exception as ex:
5815                    print(ex)
5816                    print(
5817                        '\nINFO: Elasticity evaluation failure in ',
5818                        self.__reactions__[react],
5819                        self.__species__[met],
5820                    )
5821                    print('Elasticity has been set to zero')
5822                    print(
5823                        'A stepsize that is too large might cause this ... try decreasing the factor and or min stepsize'
5824                    )
5825                    print(
5826                        'Keep in mind machine precision, is',
5827                        self.__settings__['mach_floateps'],
5828                        ' and if min stepsize',
5829                    )
5830                    print('becomes too small numeric error can become significant')
5831                    a = 0.0
5832                if abs(a) >= self.__settings__['mach_floateps']:
5833                    if self.__settings__['display_debug'] == 1:
5834                        print('species: ' + self.__species__[met])
5835                        print(
5836                            '--> d('
5837                            + self.__reactions__[react]
5838                            + ')d('
5839                            + self.__species__[met]
5840                            + ') = '
5841                            + str(a)
5842                        )
5843                    self.__evmatrix__[react, met] = a
5844
5845        # restore variables to ss values only if steady state used
5846        if self.__settings__['elas_evar_remap']:
5847            eval(compile(self.__remaps, '__remaps', 'exec'))
5848            # print "\nREMAPPING\n"
5849
5850        self.elas_var_u = self.__evmatrix__
5851
5852        # If scaled mca is requested
5853        if self.__settings__['mode_mca_scaled'] == 1:
5854            Ds = numpy.zeros((len(input), len(input)), 'd')
5855            Dj = numpy.zeros((len(input2), len(input2)), 'd')
5856
5857            for x in range(0, len(input)):
5858                Ds[x, x] = input[x]
5859            # print Ds
5860
5861            for x in range(0, len(input2)):
5862                Dj[x, x] = 1.0 / input2[x]
5863                if self.__settings__['elas_scaling_div0_fix'] and numpy.isinf(Dj[x, x]):
5864                    print(
5865                        'Infinite elasticity detected during scaling setting to zero ({})'.format(
5866                            self.reactions[x]
5867                        )
5868                    )
5869                    Dj[x, x] = 1.0e-16
5870            # print Dj
5871
5872            Dj_e = numpy.dot(Dj, self.__evmatrix__)
5873            self.__evmatrix__ = numpy.dot(Dj_e, Ds)
5874            self.elas_var = self.__evmatrix__
5875        else:
5876            self.elas_var = None
5877
5878        if self.__settings__["elas_evar_upsymb"] == 1:
5879            # Upload elasticity symbols into namespace
5880            r, c = self.__evmatrix__.shape
5881            output2 = ''
5882            for x in range(0, r):
5883                react = self.__reactions__[x]
5884                for y in range(0, c):
5885                    met = self._D_s_Order[y]
5886                    if self.__settings__['mode_mca_scaled']:
5887                        setattr(
5888                            self,
5889                            'ec' + react + '_' + met.replace('self.', ''),
5890                            self.__evmatrix__[x, y],
5891                        )
5892                    else:
5893                        setattr(
5894                            self,
5895                            'uec' + react + '_' + met.replace('self.', ''),
5896                            self.__evmatrix__[x, y],
5897                        )
5898
5899            # the new way of doing things (test phase) brett - 2007
5900            self.ec = BagOfStuff(
5901                self.__evmatrix__, self.elas_var_row, self.elas_var_col
5902            )
5903            self.ec.load()
5904            if self.__settings__['mode_mca_scaled'] == 1:
5905                self.ec.scaled = True
5906            else:
5907                self.ec.scaled = False
5908
5909            if self.__settings__['display_debug'] == 1:
5910                print('\n\n********************************\n')
5911                print(output2)
5912                print('\n********************************\n')
5913        else:
5914            pass
5915            # print 'INFO: variable elasticity symbols not attached - .__settings__["elas_evar_upsymb"] = ' + `self.__settings__["elas_evar_upsymb"]`
5916
5917        if self.__settings__['display_debug'] == 1:
5918            print('\ne_vmatrix')
5919            print(repr(self._D_s_Order).replace('self.', ''))
5920            print(self.__reactions__)
5921            print(self.__evmatrix__)
5922
5923    def CleanNaNsFromArray(self, arr, replace_val=0.0):
5924        """
5925        Scan a matrix for NaN's and replace with zeros:
5926
5927         - *arr* the array to be cleaned
5928
5929        """
5930        nantest = numpy.isnan(arr)
5931        if nantest.any():
5932            for r in range(nantest.shape[0]):
5933                if nantest[r].any():
5934                    for c in range(nantest.shape[1]):
5935                        if numpy.isnan(arr[r, c]):
5936                            arr[r, c] = replace_val
5937
5938    def EvalEpar(self, input=None, input2=None):
5939        """
5940        EvalEpar(input=None,input2=None)
5941
5942        Calculate reaction elasticities towards the parameters.
5943
5944        Both inputs (input1=species,input2=rates) should be valid (steady state for MCA) solutions and given in the correct order for them to be used. If either or both are missing the last state values are used automatically. Elasticities are scaled using input 1 and 2.
5945
5946        Arguments:
5947
5948         - input [default=None]: species concentration vector
5949         - input2 [default=None]: reaction rate vector
5950
5951        Settings, set in mod.__settings__::
5952
5953         - elas_epar_upsymb   [default = 1] attach individual elasticity symbols to model instance
5954         - elas_scaling_div0_fix [default=False] if NaN's are detected in the variable and parameter elasticity matrix replace with zero
5955
5956        """
5957        if input == None or input2 == None:
5958            input = self.state_species
5959            input2 = self.state_flux
5960        else:
5961            assert len(input) == len(self.species), (
5962                'length error this array must have '
5963                + str(len(self.species))
5964                + ' elements'
5965            )
5966            assert len(input2) == len(self.reactions), (
5967                'length error this array must have '
5968                + str(len(self.reactions))
5969                + ' elements'
5970            )
5971            # put in to fix the juicy bug - brett 20040930
5972            # iow automatic derivatives use the current values of self.Sx to operate
5973            exec(self.__CDvarUpString__)
5974
5975        # create parameter holding array
5976        parVal_hold = numpy.zeros((len(self.__parameters__)), 'd')
5977        if self.__settings__['display_debug'] == 1:
5978            print('\nParinput = ' + repr(input) + '\n')
5979            print('\nparVal_hold1')
5980            print(parVal_hold)
5981
5982        # Store parameter values into the storage array and copy them to the working array (parVal2)
5983        exec(self.__par_map2storeC)
5984        parVal2 = copy.copy(parVal_hold)
5985
5986        # create the matrix of parameter elasticities
5987        self.__epmatrix__ = numpy.zeros(
5988            (len(self.__reactions__), len(self.__parameters__)), 'd'
5989        )
5990
5991        # create tuples of the rows and columns for the Ep matrix
5992        self.elas_par_row = tuple(copy.copy(self.__reactions__))
5993        self.elas_par_col = tuple(self.__parameters__)
5994
5995        for react in range(len(self.__reactions__)):
5996            if self.__settings__['display_debug'] == 1:
5997                print('\nReaction: ' + self.__reactions__[react])
5998            for par in range(len(self.__parameters__)):
5999                countV = 0
6000                countPar = 0
6001                try:
6002                    """ I got the idea of scaling the stepsize to So from Herbert Sauro's Jarnac TModel.uEEOp function
6003
6004                    brett - 20040818"""
6005
6006                    hstep = (
6007                        getattr(self, self.__parameters__[par])
6008                        * self.__settings__['mode_elas_deriv_factor']
6009                    )  # self.__settings__['mode_elas_deriv_factor'] = 0.0001
6010                    if (
6011                        abs(hstep) < self.__settings__['mode_elas_deriv_min']
6012                    ):  # self.__settings__['mode_elas_deriv_min'] = 1.0e-12
6013                        hstep = self.__settings__['mode_elas_deriv_min']
6014
6015                    a = ScipyDerivative(
6016                        self.__num_deriv_function__,
6017                        getattr(self, self.__parameters__[par]),
6018                        order=self.__settings__['mode_elas_deriv_order'],
6019                        dx=hstep,
6020                        n=1,
6021                        args=(self.__reactions__[react], self.__parameters__[par]),
6022                    )
6023                except Exception as ex:
6024                    print(
6025                        '\nNumeric derivative evaluation failure in ',
6026                        self.__reactions__[react],
6027                        self.__parameters__[par],
6028                    )
6029                    print('Elasticity has been set to NaN')
6030                    print(
6031                        'A stepsize that is too large might cause this ... try decreasing the factor and or min stepsize'
6032                    )
6033                    print(
6034                        'Keep in mind machine precision, is',
6035                        self.__settings__['mach_floateps'],
6036                        ' and if min stepsize',
6037                    )
6038                    print('becomes too small numeric error can become significant')
6039                    print(ex)
6040                    a = numpy.NaN
6041
6042                if numpy.isnan(a) or abs(a) > self.__settings__['mach_floateps']:
6043                    if self.__settings__['display_debug'] == 1:
6044                        print('parameter: ' + self.__parameters__[par])
6045                        print(
6046                            '--> d('
6047                            + self.__reactions__[react]
6048                            + ')d('
6049                            + self.__parameters__[par]
6050                            + ') = '
6051                            + repr(a)
6052                        )
6053                    self.__epmatrix__[react, par] = a
6054
6055        self.elas_par_u = self.__epmatrix__
6056
6057        # Retrieve parameter values from the storage array
6058        exec(self.__par_remapC)
6059
6060        if self.__settings__['display_debug'] == 1:
6061            print('\nparVal_hold2')
6062            print(parVal_hold)
6063
6064        # Parameters are scaled by [1/J]*[Ep]*[P]
6065        # If scaled mca is requested scale self.__epmatrix__
6066        if self.__settings__['mode_mca_scaled'] == 1:
6067            Dp = numpy.zeros((len(self.__parameters__), len(self.__parameters__)), 'd')
6068            Dj = numpy.zeros((len(input2), len(input2)), 'd')
6069
6070            for x in range(0, len(self.__parameters__)):
6071                Dp[x, x] = parVal_hold[x]
6072            # print Dp
6073
6074            for x in range(0, len(input2)):
6075                Dj[x, x] = 1.0 / input2[x]
6076                if self.__settings__['elas_scaling_div0_fix'] and numpy.isinf(Dj[x, x]):
6077                    print(
6078                        'Infinite elasticity detected during scaling setting to zero ({})'.format(
6079                            self.reactions[x]
6080                        )
6081                    )
6082                    Dj[x, x] = 1.0e-16
6083            # print Dj
6084
6085            Dj_e = numpy.dot(Dj, self.__epmatrix__)
6086            self.__epmatrix__ = numpy.dot(Dj_e, Dp)
6087            self.elas_par = self.__epmatrix__
6088        else:
6089            self.elas_par = None
6090
6091        del parVal_hold
6092
6093        if self.__settings__["elas_epar_upsymb"] == 1:
6094            # Upload elasticity symbols into namespace
6095            r, c = self.__epmatrix__.shape
6096            output2 = ''
6097            for x in range(0, r):
6098                react = self.__reactions__[x]
6099                for y in range(0, c):
6100                    met = self.__parameters__[y]
6101                    if self.__settings__['mode_mca_scaled']:
6102                        setattr(
6103                            self,
6104                            'ec' + react + '_' + met.replace('self.', ''),
6105                            self.__epmatrix__[x, y],
6106                        )
6107                    else:
6108                        setattr(
6109                            self,
6110                            'uec' + react + '_' + met.replace('self.', ''),
6111                            self.__epmatrix__[x, y],
6112                        )
6113
6114            # the new way of doing things (test phase) brett - 2007
6115            self.ecp = BagOfStuff(
6116                self.__epmatrix__, self.elas_par_row, self.elas_par_col
6117            )
6118            self.ecp.load()
6119            if self.__settings__['mode_mca_scaled'] == 1:
6120                self.ecp.scaled = True
6121            else:
6122                self.ecp.scaled = False
6123
6124            if self.__settings__['display_debug'] == 1:
6125                print('\n\n********************************\n')
6126                print(output2)
6127                print('\n********************************\n')
6128
6129        if self.__settings__['display_debug'] == 1:
6130            print('\ne_pmatrix')
6131            print(self.__parameters__)
6132            print(self.__reactions__)
6133            print(self.__epmatrix__)
6134
6135    def showEvar(self, File=None):
6136        """
6137        showEvar(File=None)
6138
6139        Write out all variable elasticities as \'LaTeX\' formatted strings, alternatively write results to a file.
6140
6141        Arguments:
6142
6143        File [default=None]: an open writable Python file object
6144
6145        """
6146        # The Variable elasticities
6147        try:
6148            r, c = self.__evmatrix__.shape
6149            evar_output = ''
6150            for x in range(0, r):
6151                react = self.__reactions__[x]
6152                evar_output += '\n' + repr(self.__reactions__[x]) + '\n'
6153                for y in range(0, c):
6154                    rtemp = (
6155                        self.__settings__['mode_number_format']
6156                        % self.__evmatrix__[x, y]
6157                    )
6158                    met = self._D_s_Order[y]
6159                    if self.__evmatrix__[x, y] != 0.0:
6160                        if self.__settings__['mode_mca_scaled']:
6161                            elas = '\\ec{' + react + '}{' + met + '} = ' + rtemp
6162                        else:
6163                            elas = '\\uec{' + react + '}{' + met + '} = ' + rtemp
6164                        evar_output += elas + '\n'
6165            evar_output = evar_output.replace('self.', '')
6166        except:
6167            evar_output = 'No variable elasticities - run EvalEvar() to calculate'
6168
6169        if File != None:
6170            print('\nspecies elasticities')
6171            File.write('\n## species elasticities\n')
6172            File.write(evar_output)
6173        else:
6174            print('\nspecies elasticities')
6175            print(evar_output)
6176
6177    def showEpar(self, File=None):
6178        """
6179        showEpar(File=None)
6180
6181        Write out all nonzero parameter elasticities as \'LaTeX\' formatted strings, alternatively write to file.
6182
6183        Arguments:
6184
6185        File [default=None]: an open writable Python file object
6186
6187        """
6188        # The parameter elasticities
6189        try:
6190            r, c = self.__epmatrix__.shape
6191            epar_output = ''
6192            for x in range(0, r):
6193                react = self.__reactions__[x]
6194                epar_output += '\n' + repr(self.__reactions__[x]) + '\n'
6195                for y in range(0, c):
6196                    rtemp = (
6197                        self.__settings__['mode_number_format']
6198                        % self.__epmatrix__[x, y]
6199                    )
6200                    met = self.__parameters__[y]
6201                    # brett (paranoia removal) October 2004
6202                    # if abs(self.__epmatrix__[x,y]) >= 1.e-15:
6203                    if self.__settings__['mode_mca_scaled']:
6204                        elas = '\\ec{' + react + '}{' + met + '} = ' + rtemp
6205                    else:
6206                        elas = '\\uec{' + react + '}{' + met + '} = ' + rtemp
6207                        # print elas
6208                    epar_output += elas + '\n'
6209            epar_output = epar_output.replace('self.', '')
6210        except:
6211            epar_output = 'No parameter elasticities - run EvalEpar() to calculate'
6212
6213        if File != None:
6214            print('\nParameter elasticities')
6215            File.write('\n## Parameter elasticities\n')
6216            File.write(epar_output)
6217        else:
6218            print('\nParameter elasticities')
6219            print(epar_output)
6220
6221    def showElas(self, File=None):
6222        """
6223        showElas(File=None)
6224
6225        Print all elasticities to screen or file as \'LaTeX\' compatible strings.
6226        Calls showEvar() and showEpar()
6227
6228        Arguments:
6229
6230        File [default=None]: an open writable Python file object
6231
6232        """
6233        if File != None:
6234            self.showEvar(File)
6235            self.showEpar(File)
6236        else:
6237            self.showEvar()
6238            self.showEpar()
6239
6240    def ScaleKL(self, input, input2):
6241        """
6242        ScaleKL(input,input2)
6243
6244        Scale the K and L matrices with current steady state (if either input1 or 2 == None) or user input.
6245
6246        Arguments:
6247
6248        input: vector of species concentrations
6249        input2: vector of reaction rates
6250
6251        """
6252        # called by EvalEvar
6253        # Scale L matrix
6254        lmat = copy.copy(self.__lmatrix__)
6255
6256        if type(input) == type(None) or type(input2) == type(None):
6257            input = self.state_species
6258            input2 = self.state_flux
6259            # print 'INFO: Using state_species and state_flux as input'
6260        else:
6261            assert len(input) == len(self.species), (
6262                'length error this array must have '
6263                + str(len(self.species))
6264                + ' elements'
6265            )
6266            assert len(input2) == len(self.reactions), (
6267                'length error this array must have '
6268                + str(len(self.reactions))
6269                + ' elements'
6270            )
6271
6272        s_1_scale = numpy.zeros((len(input), len(input)), 'd')
6273
6274        for x in range(0, len(input)):
6275            try:
6276                s_1_scale[x, x] = (
6277                    1.0 / input[self.lmatrix_row[x]]
6278                )  # create 1/D Using the steady-state met's ordered to the rows
6279            except:
6280                print(
6281                    'Zero species detected: '
6282                    + self.__species__[self.lmatrix_row[x]]
6283                    + '_ss = '
6284                    + repr(input[self.lmatrix_row[x]])
6285                )
6286                s_1_scale[x, x] = 0.0
6287
6288        Si_order = numpy.zeros(len(self.lmatrix_col))  # check
6289        Si_scale = numpy.zeros((len(self.lmatrix_col), len(self.lmatrix_col)), 'd')
6290
6291        for x in range(0, len(self.lmatrix_col)):
6292            Si_order[x] = self.lmatrix_col[x]  # check
6293            Si_scale[x, x] = input[self.lmatrix_col[x]]
6294
6295        L_Si = numpy.dot(lmat, Si_scale)
6296        L_scaled = numpy.dot(s_1_scale, L_Si)
6297
6298        self.lmatrix_scaled = L_scaled
6299
6300        # Scale K matrix
6301        kmat = copy.copy(self.__kmatrix__)
6302
6303        j_1_scale = numpy.zeros((len(input2), len(input2)), 'd')
6304
6305        for x in range(0, len(input2)):
6306            try:
6307                j_1_scale[x, x] = 1.0 / input2[self.kmatrix_row[x]]
6308            except:
6309                print(
6310                    '\nNull flux detected: '
6311                    + self.__reactions__[self.kmatrix_row[x]]
6312                    + '_ss = '
6313                    + repr(input2[self.kmatrix_row[x]])
6314                )
6315                j_1_scale[x, x] = 0.0
6316
6317        Ji_order = numpy.zeros(len(self.kmatrix_col))  # check
6318        Ji_scale = numpy.zeros((len(self.kmatrix_col), len(self.kmatrix_col)), 'd')
6319
6320        for x in range(0, len(self.kmatrix_col)):
6321            Ji_order[x] = self.kmatrix_col[x]  # check
6322            Ji_scale[x, x] = input2[self.kmatrix_col[x]]
6323
6324        K_Ji = numpy.dot(kmat, Ji_scale)
6325        K_scaled = numpy.dot(j_1_scale, K_Ji)
6326
6327        self.kmatrix_scaled = K_scaled
6328
6329    def __num_deriv_function__(self, x, react, met):
6330        """
6331        __num_deriv_function__(x,react,met)
6332
6333        System function that evaluates the rate equation, used by numeric perturbation methods to derive
6334        elasticities. It uses a specific format assigning x to met and evaluating react for v and is tailored for the ScipyDerivative() function using precompiled function strings.
6335
6336        Arguments:
6337
6338        x: value to assign to met
6339        react: reaction
6340        met: species
6341
6342        """
6343        # exec(met)
6344        # self.Forcing_Function()
6345        # exec(react)
6346        # return v
6347        bk = getattr(self, met)  # backup current metabolite value
6348        setattr(self, met, x)
6349        self.Forcing_Function()
6350        v = getattr(self, react)
6351        vout = v()
6352        setattr(self, met, bk)  # reset metabolite value
6353        return vout
6354
6355    #   Control coefficient routines
6356    def EvalCC(self):
6357        """
6358        EvalCC()
6359
6360        Calculate the MCA control coefficients using the current steady-state solution.
6361
6362        mod.__settings__["mca_ccj_upsymb"] = 1  attach the flux control coefficients to the model instance
6363        mod.__settings__["mca_ccs_upsymb"] = 1 attach the concentration control coefficients to the model instance
6364
6365        Arguments:
6366        None
6367
6368        """
6369        # sort E to match K and L
6370        e2 = numpy.zeros((len(self.kmatrix_row), len(self.lmatrix_row)), 'd')
6371        # print e2
6372
6373        for x in range(0, len(self.kmatrix_row)):
6374            for y in range(0, len(self.lmatrix_row)):
6375                e2[x, y] = self.elas_var_u[self.kmatrix_row[x], self.lmatrix_row[y]]
6376
6377        if self.__settings__['display_debug'] == 1:
6378            print(self.lmatrix_row)
6379            print(self.kmatrix_row)
6380            print('---')
6381            print(self.__nmatrix__.shape)
6382            print(self.nmatrix_row)
6383            print(self.nmatrix_col)
6384            print(self.nmatrix)
6385            print('---')
6386            print(self.__lmatrix__.shape)
6387            print(self.__lmatrix__)
6388            print('---')
6389            print(self.__kmatrix__.shape)
6390            print(self.__kmatrix__)
6391            print('---')
6392            print(e2.shape)
6393            print(e2)
6394
6395        EL = -1.0 * numpy.dot(e2, self.__lmatrix__)
6396        KEL = numpy.concatenate((self.__kmatrix__, EL), 1)
6397        self.kel_unscaled = KEL
6398
6399        go = 0
6400        try:
6401            Ci_u = scipy.linalg.inv(KEL)
6402            # fortran has a very fp idea of zero I use abs(val)<1.0e-15
6403            self.__structural__.MatrixFloatFix(Ci_u, val=1.0e-15)
6404            go = 1
6405        except Exception as ex:
6406            print(ex)
6407            print(
6408                '\nINFO: K-EL matrix inversion failed this is possibly due to NaN values in the Elasticity matrix'
6409            )
6410            print(
6411                'NaN elasticities can be caused by zero fluxes or concentrations look at the settings (in mod.__settings__)'
6412            )
6413            print('\'elas_scaling_div0_fix\' and \'elas_zero_flux_fix\'')
6414            go = 0
6415
6416        if go == 1 and not self.__settings__['mode_mca_scaled']:
6417            self.mca_ci = Ci_u
6418            self.__mca_CCi_unscaled = Ci_u
6419            del Ci_u
6420        elif go == 1 and self.__settings__['mode_mca_scaled']:
6421            Vi_l = self.__kmatrix__.shape[1]  # Ind fluxes
6422            Vd_l = abs(
6423                self.__kmatrix__.shape[0] - self.__kmatrix__.shape[1]
6424            )  # Can never be the case but b.paranoid
6425            Si_l = self.__lmatrix__.shape[1]  # Ind species
6426            Sd_l = abs(
6427                self.__lmatrix__.shape[0] - self.__lmatrix__.shape[1]
6428            )  # Can never be the case but b.paranoid
6429
6430            scale1_l = Vi_l + Si_l
6431            scale1 = numpy.zeros((scale1_l, scale1_l), 'd')
6432
6433            for x in range(0, Vi_l):
6434                scale1[x, x] = 1.0 / self.state_flux[self.kmatrix_row[x]]
6435            for x in range(Vi_l, scale1_l):
6436                scale1[x, x] = 1.0 / self.state_species[self.lmatrix_row[x - Vi_l]]
6437
6438            scale2 = numpy.zeros((Vi_l + Vd_l, Vi_l + Vd_l), 'd')
6439
6440            for x in range(0, len(self.state_flux)):
6441                scale2[x, x] = self.state_flux[self.kmatrix_row[x]]
6442
6443            left = numpy.dot(scale1, Ci_u)
6444            Ci = numpy.dot(left, scale2)
6445
6446            self.mca_ci = Ci
6447            del scale1_l, scale1, scale2, left, Ci, Ci_u
6448
6449        Cirow = []
6450        Cicol = []
6451
6452        # the wierdness continues
6453        for x in range(0, len(self.kmatrix_row)):
6454            Cicol.append(self.__reactions__[self.kmatrix_row[x]])
6455
6456        for x in range(0, len(self.kmatrix_col)):
6457            Cirow.append(self.__reactions__[self.kmatrix_col[x]])
6458
6459        for x in range(0, len(self.lmatrix_col)):
6460            Cirow.append(self._D_s_Order[self.lmatrix_col[x]].replace('self.', ''))
6461
6462        self.mca_ci_row = Cirow
6463        self.mca_ci_col = Cicol
6464
6465        del e2, EL, KEL
6466
6467        if self.__settings__['display_debug'] == 1:
6468            print('print self.mca_ci_row')
6469            print(self.mca_ci_row)
6470            print('print self.mca_ci_col')
6471            print(self.mca_ci_col)
6472            print('print self.mca_ci')
6473            print(self.mca_ci)
6474
6475        CJi = numpy.zeros((len(self.kmatrix_col), self.mca_ci.shape[1]), 'd')
6476        CSi = numpy.zeros(
6477            (self.mca_ci.shape[0] - len(self.kmatrix_col), self.mca_ci.shape[1]), 'd'
6478        )
6479
6480        for x in range(0, self.mca_ci.shape[0]):
6481            for y in range(0, self.mca_ci.shape[1]):
6482                if x < len(self.kmatrix_col):
6483                    CJi[x, y] = self.mca_ci[x, y]
6484                else:
6485                    CSi[x - len(self.kmatrix_col), y] = self.mca_ci[x, y]
6486
6487        Ko_row = []
6488        Ko_col = []
6489        sKo = numpy.zeros(self.__kzeromatrix__.shape, 'd')
6490        xFactor = self.__kmatrix__.shape[0] - self.__kzeromatrix__.shape[0]
6491
6492        # we need the scaled k/l matrices to calculate the dependent cc's
6493        # self.ScaleKL()
6494
6495        for x in range(self.__kzeromatrix__.shape[0] - 1, -1, -1):
6496            Ko_row.append(self.__reactions__[self.kzeromatrix_row[x]])
6497            for y in range(
6498                self.__kzeromatrix__.shape[1] - 1, -1, -1
6499            ):  # this can be replaced by a row slice operation
6500                sKo[x, y] = self.kmatrix_scaled[x + xFactor, y]
6501                if x == 0:
6502                    Ko_col.append(self.__reactions__[self.kzeromatrix_col[y]])
6503
6504        Ko_row.reverse()
6505        Ko_col.reverse()
6506
6507        if self.__settings__['display_debug'] == 1:
6508            print('CJi')
6509            print(CJi)
6510            print('sKo')
6511            print(sKo)
6512            print('self.kzeromatrix')
6513            print(self.__kzeromatrix__)
6514
6515        # new
6516        if self.__settings__['mode_mca_scaled']:
6517            self.mca_cjd = numpy.dot(sKo, CJi)
6518        else:
6519            self.mca_cjd = numpy.dot(self.__kzeromatrix__, CJi)
6520
6521        self.mca_cjd_row = Ko_row
6522        self.mca_cjd_col = copy.copy(self.mca_ci_col)
6523
6524        del sKo, CJi
6525
6526        if self.__settings__['display_debug'] == 1:
6527            print('self.mca_cjd_row')
6528            print(self.mca_cjd_row)
6529            print('self.mca_cjd_col')
6530            print(self.mca_cjd_col)
6531            print('self.mca_cjd')
6532            print(self.mca_cjd)
6533
6534        Lo_row = []
6535        Lo_col = []
6536        sLo = numpy.zeros(self.__lzeromatrix__.shape, 'd')
6537        xFactor = self.__lmatrix__.shape[0] - self.__lzeromatrix__.shape[0]
6538        for x in range(self.__lzeromatrix__.shape[0] - 1, -1, -1):
6539            Lo_row.append(
6540                self._D_s_Order[self.lzeromatrix_row[x]].replace('self.', '')
6541            )
6542            for y in range(
6543                self.__lzeromatrix__.shape[1] - 1, -1, -1
6544            ):  # this can be replaced by a row slice operation
6545                sLo[x, y] = self.lmatrix_scaled[x + xFactor, y]
6546                if x == 0:
6547                    Lo_col.append(self._D_s_Order[self.lzeromatrix_col[y]])
6548
6549        Lo_row.reverse()
6550        Lo_col.reverse()
6551
6552        if (
6553            self.__HAS_MOIETY_CONSERVATION__ == True
6554        ):  # Only do this if there is S dependency
6555            if self.__settings__['mode_mca_scaled']:
6556                self.mca_csd = numpy.dot(sLo, CSi)
6557            else:
6558                self.mca_csd = numpy.dot(self.__lzeromatrix__, CSi)
6559            self.mca_csd_row = Lo_row
6560            self.mca_csd_col = copy.copy(self.mca_ci_col)
6561        else:
6562            self.mca_csd = None
6563            self.mca_csd_row = None
6564            self.mca_csd_col = None
6565
6566        del CSi, sLo
6567
6568        if self.__settings__['display_debug'] == 1:
6569            print('self.mca_csd')
6570            print(self.mca_csd)
6571            print('self.mca_csd_row')
6572            print(self.mca_csd_row)
6573            print('self.mca_csd_col')
6574            print(self.mca_csd_col)
6575
6576        if self.__HAS_FLUX_CONSERVATION__ and self.__HAS_MOIETY_CONSERVATION__:
6577            self.cc_flux = numpy.concatenate(
6578                (self.mca_ci[: self.__kmatrix__.shape[1], :], self.mca_cjd)
6579            )
6580            self.cc_flux_row = copy.copy(
6581                self.mca_ci_row[: self.__kmatrix__.shape[1]] + self.mca_cjd_row
6582            )
6583            self.cc_flux_col = copy.copy(self.mca_ci_col)
6584            self.cc_conc = numpy.concatenate(
6585                (self.mca_ci[self.__kmatrix__.shape[1] :, :], self.mca_csd)
6586            )
6587            self.cc_conc_row = copy.copy(
6588                self.mca_ci_row[self.__kmatrix__.shape[1] :] + self.mca_csd_row
6589            )
6590            self.cc_conc_col = copy.copy(self.mca_ci_col)
6591        elif self.__HAS_FLUX_CONSERVATION__:
6592            self.cc_flux = numpy.concatenate(
6593                (self.mca_ci[: self.__kmatrix__.shape[1], :], self.mca_cjd)
6594            )
6595            self.cc_flux_row = copy.copy(
6596                self.mca_ci_row[: self.__kmatrix__.shape[1]] + self.mca_cjd_row
6597            )
6598            self.cc_flux_col = copy.copy(self.mca_ci_col)
6599            self.cc_conc = copy.copy(self.mca_ci[self.__kmatrix__.shape[1] :, :])
6600            self.cc_conc_row = copy.copy(self.mca_ci_row[self.__kmatrix__.shape[1] :])
6601            self.cc_conc_col = copy.copy(self.mca_ci_col)
6602        elif self.__HAS_MOIETY_CONSERVATION__:
6603            print(
6604                'INFO: this is interesting no dependent flux cc\'s only dependent conc cc\'s!'
6605            )
6606            self.cc_flux = copy.copy(self.mca_ci[: self.__kmatrix__.shape[1], :])
6607            self.cc_flux_row = copy.copy(self.mca_ci_row[: self.__kmatrix__.shape[1]])
6608            self.cc_flux_col = copy.copy(self.mca_ci_col)
6609            self.cc_conc = numpy.concatenate(
6610                (self.mca_ci[self.__kmatrix__.shape[1] :, :], self.mca_csd)
6611            )
6612            self.cc_conc_row = copy.copy(
6613                self.mca_ci_row[self.__kmatrix__.shape[1] :] + self.mca_csd_row
6614            )
6615            self.cc_conc_col = copy.copy(self.mca_ci_col)
6616        else:
6617            print('INFO: this is interesting no dependent flux/conc coefficients!')
6618            self.cc_flux = copy.copy(self.mca_ci[: self.__kmatrix__.shape[1], :])
6619            self.cc_flux_row = copy.copy(self.mca_ci_row[: self.__kmatrix__.shape[1]])
6620            self.cc_flux_col = copy.copy(self.mca_ci_col)
6621            self.cc_conc = copy.copy(self.mca_ci[self.__kmatrix__.shape[1] :, :])
6622            self.cc_conc_row = copy.copy(self.mca_ci_row[self.__kmatrix__.shape[1] :])
6623            self.cc_conc_col = copy.copy(self.mca_ci_col)
6624
6625        self.cc_all = numpy.concatenate((self.cc_flux, self.cc_conc))
6626        self.cc_all_row = self.cc_flux_row + self.cc_conc_row
6627        self.cc_all_col = copy.copy(self.mca_ci_col)
6628
6629        # the new way of doing things (test phase) brett - 2007
6630        self.cc = BagOfStuff(self.cc_all, self.cc_all_row, self.cc_all_col)
6631        self.cc.load()
6632        if self.__settings__['mode_mca_scaled'] == 1:
6633            self.cc.scaled = True
6634        else:
6635            self.cc.scaled = False
6636
6637        if self.__settings__['display_debug'] == 1:
6638            print('self.cc_flux')
6639            print(self.cc_flux_row)
6640            print(self.cc_flux_col)
6641            print(self.cc_flux.shape)
6642
6643            print('self.cc_conc')
6644            print(self.cc_conc_row)
6645            print(self.cc_conc_col)
6646            print(self.cc_conc.shape)
6647
6648            print('self.cc_all')
6649            print(self.cc_all_row)
6650            print(self.cc_all_col)
6651            print(self.cc_all.shape)
6652
6653        if self.__settings__["mca_ccj_upsymb"]:
6654            # CJ
6655            r, c = self.cc_all.shape
6656            CJoutput = ''
6657            for x in range(0, len(self.__reactions__)):
6658                for y in range(0, c):
6659                    if self.__settings__['mode_mca_scaled']:
6660                        setattr(
6661                            self,
6662                            'ccJ' + self.cc_all_row[x] + '_' + self.cc_all_col[y],
6663                            self.cc_all[x, y],
6664                        )
6665                    else:
6666                        setattr(
6667                            self,
6668                            'uccJ' + self.cc_all_row[x] + '_' + self.cc_all_col[y],
6669                            self.cc_all[x, y],
6670                        )
6671
6672        if self.__settings__["mca_ccs_upsymb"]:
6673            # CS
6674            CSoutput = ''
6675            for x in range(len(self.__reactions__), r):
6676                for y in range(0, c):
6677                    if self.__settings__['mode_mca_scaled']:
6678                        setattr(
6679                            self,
6680                            'cc' + self.cc_all_row[x] + '_' + self.cc_all_col[y],
6681                            self.cc_all[x, y],
6682                        )
6683                    else:
6684                        setattr(
6685                            self,
6686                            'ucc' + self.cc_all_row[x] + '_' + self.cc_all_col[y],
6687                            self.cc_all[x, y],
6688                        )
6689
6690        if self.__settings__['display_debug'] == 1:
6691            print('CJoutput')
6692            print(CJoutput)
6693            print('CSoutput')
6694            print(CSoutput)
6695
6696    def showCC(self, File=None):
6697        """
6698        showCC(File=None)
6699
6700        Print all control coefficients as \'LaTex\' formatted strings to the screen or file.
6701
6702        Arguments:
6703
6704        File [default=None]: an open, writable Python file object
6705
6706        """
6707        r, c = self.cc_all.shape
6708        if self.__settings__["mca_ccall_altout"]:
6709            CAltoutput = ''
6710            for x in range(0, c):
6711                col = self.cc_all_col[x]
6712                CAltoutput += '\n`Reaction ' + col + '\'\n'
6713                for y in range(0, r):
6714                    if y == 0:
6715                        CAltoutput += '\"Flux control coefficients\"\n'
6716                    if y == len(self.__reactions__):
6717                        CAltoutput += '\"Concentration control coefficients\"\n'
6718                    row = self.cc_all_row[y]
6719                    rtemp = self.__settings__['mode_number_format'] % self.cc_all[y, x]
6720                    if y < len(self.__reactions__):
6721                        if self.__settings__['mode_mca_scaled']:
6722                            cc = '\\cc{J' + row + '}{' + col + '} = ' + rtemp
6723                        else:
6724                            cc = '\\ucc{J' + row + '}{' + col + '} = ' + rtemp
6725                    else:
6726                        if self.__settings__['mode_mca_scaled']:
6727                            cc = '\\cc{' + row + '}{' + col + '} = ' + rtemp
6728                        else:
6729                            cc = '\\ucc{' + row + '}{' + col + '} = ' + rtemp
6730                    CAltoutput += cc + '\n'
6731        else:
6732            if self.__settings__["mca_ccall_fluxout"]:
6733                # CJ
6734                CJoutput = ''
6735                for x in range(0, len(self.__reactions__)):
6736                    row = self.cc_all_row[x]
6737                    CJoutput += '\n`J' + row + '\'\n'
6738                    for y in range(0, c):
6739                        col = self.cc_all_col[y]
6740                        rtemp = (
6741                            self.__settings__['mode_number_format'] % self.cc_all[x, y]
6742                        )
6743                        if self.__settings__['mode_mca_scaled']:
6744                            cc = '\\cc{J' + row + '}{' + col + '} = ' + rtemp
6745                        else:
6746                            cc = '\\ucc{J' + row + '}{' + col + '} = ' + rtemp
6747                        CJoutput += cc + '\n'
6748            if self.__settings__["mca_ccall_concout"]:
6749                # CS
6750                CSoutput = ''
6751                for x in range(len(self.__reactions__), r):
6752                    row = self.cc_all_row[x]
6753                    CSoutput += '\n`' + row + '\'\n'
6754                    for y in range(0, c):
6755                        col = self.cc_all_col[y]
6756                        rtemp = (
6757                            self.__settings__['mode_number_format'] % self.cc_all[x, y]
6758                        )
6759                        if self.__settings__['mode_mca_scaled']:
6760                            cc = '\\cc{' + row + '}{' + col + '} = ' + rtemp
6761                        else:
6762                            cc = '\\ucc{' + row + '}{' + col + '} = ' + rtemp
6763                        CSoutput += cc + '\n'
6764        if File != None:
6765            # assert type(File) == file, 'showCC() needs an open file object'
6766            if self.__settings__["mca_ccall_altout"]:
6767                print('\nControl coefficients grouped by reaction')
6768                File.write('\n## Control coefficients grouped by reaction\n')
6769                File.write(CAltoutput)
6770            else:
6771                if self.__settings__["mca_ccall_fluxout"]:
6772                    print('\nFlux control coefficients')
6773                    File.write('\n## Flux control coefficients\n')
6774                    File.write(CJoutput)
6775                if self.__settings__["mca_ccall_concout"]:
6776                    print('\nConcentration control coefficients')
6777                    File.write('\n## Concentration control coefficients\n')
6778                    File.write(CSoutput)
6779        else:
6780            if self.__settings__["mca_ccall_altout"]:
6781                print('\nControl coefficients grouped by reaction')
6782                print(CAltoutput)
6783            else:
6784                if self.__settings__["mca_ccall_fluxout"]:
6785                    print('\nFlux control coefficients')
6786                    print(CJoutput)
6787                if self.__settings__["mca_ccall_concout"]:
6788                    print('\nConcentration control coefficients')
6789                    print(CSoutput)
6790
6791    def EvalRC(self):
6792        """
6793        EvalRC()
6794
6795        Calculate the MCA response coefficients using the current steady-state solution.
6796
6797        Arguments:
6798        None
6799
6800        """
6801
6802        reordered_cc_all = numpy.zeros(self.cc_all.shape, 'd')
6803        for reac in range(len(self.elas_par_row)):
6804            reordered_cc_all[:, reac] = self.cc_all[
6805                :, list(self.cc_all_col).index(self.elas_par_row[reac])
6806            ]
6807
6808        self.mca_rc_par = numpy.dot(reordered_cc_all, self.elas_par)
6809        self.mca_rc_par_row = self.cc_all_row
6810        self.mca_rc_par_col = self.elas_par_col
6811        del reordered_cc_all
6812        self.__structural__.MatrixFloatFix(self.mca_rc_par)
6813        self.rc = BagOfStuff(self.mca_rc_par, self.mca_rc_par_row, self.mca_rc_par_col)
6814        self.rc.load()
6815        if self.__settings__['mode_mca_scaled'] == 1:
6816            self.rc.scaled = True
6817        else:
6818            self.rc.scaled = False
6819
6820    def EvalRCT(self):
6821        """
6822        EvalRCT()
6823
6824        Calculate the MCA response coefficients using the current steady-state solution.
6825
6826        Responses to changes in the sums of moiety conserved cycles are also calculated.
6827
6828        Arguments:
6829        None
6830
6831        """
6832
6833        # We arbitrarily choose the order of reactions as that of elas_var_row
6834        # and the order of species as that of elas_var_col. The order of dependent
6835        # species (there is one for each moiety conserved cycle) is from the
6836        # conservation matrix (mod.Consmatrix.row). The final output is the
6837        # (m x (m-r)) R^S_T matrix and the (n x (m-r)) R^J_T matrix.
6838        #
6839        # See "Metabolic control analysis in a nutshell" by Hofmeyr and also
6840        # Kholodenko, Sauro, Westerhoff 1994 for the matrix formulations.
6841        #
6842        # Danie Palm - 2011-10-26
6843
6844        # Reorder reactions in Cs and CJ to match elas_var_row
6845        reaction_reordered_cc_conc = numpy.zeros(self.cc_conc.shape, 'd')
6846        for reac in range(len(self.elas_var_row)):
6847            reaction_reordered_cc_conc[:, reac] = self.cc_conc[
6848                :, list(self.cc_conc_col).index(self.elas_var_row[reac])
6849            ]
6850        reaction_reordered_cc_flux = numpy.zeros(self.cc_flux.shape, 'd')
6851        for reac in range(len(self.elas_var_row)):
6852            reaction_reordered_cc_flux[:, reac] = self.cc_flux[
6853                :, list(self.cc_flux_col).index(self.elas_var_row[reac])
6854            ]
6855
6856        # Reorder metabolites in Cs to match elas_var_col
6857        reordered_cc_conc = numpy.zeros(self.cc_conc.shape, 'd')
6858        for metab in range(len(self.elas_var_col)):
6859            reordered_cc_conc[metab, :] = reaction_reordered_cc_conc[
6860                list(self.cc_conc_row).index(self.elas_var_col[metab]), :
6861            ]
6862
6863        # Reorder fluxes in CJ to match elas_var_row
6864        reordered_cc_flux = numpy.zeros(self.cc_flux.shape, 'd')
6865        for flux in range(len(self.elas_var_row)):
6866            reordered_cc_flux[flux, :] = reaction_reordered_cc_flux[
6867                list(self.cc_flux_row).index(self.elas_var_row[flux]), :
6868            ]
6869
6870        # Some basic dimensions and matrices
6871        m = len(self.species)
6872        r = len(self.Lmatrix.col)
6873        Im = numpy.matrix(numpy.eye(m))
6874
6875        # Construct the (right) pseudoinverse of the the conservation matrix in the order of elas_var_col
6876        reordered_zero_I = numpy.zeros((m, m - r), 'd')
6877        for species in self.elas_var_col:
6878            if species in self.Consmatrix.row:
6879                row = self.elas_var_col.index(species)
6880                col = self.Consmatrix.row.index(species)
6881                reordered_zero_I[row, col] = 1.0
6882
6883        # Unlike normal RCs, separate calculations are required for scaled
6884        # and unscaled RCT's. We also need to pick the right elasticity
6885        # matrix: elas_var for scaled and elas_var_u for unscaled.
6886        if self.__settings__['mode_mca_scaled'] == 1:
6887            # Some scaling matrices
6888            diag_T = None
6889            if self.__KeyWords__['Species_In_Conc']:
6890                diag_T = numpy.matrix(numpy.diag(self.__tvec_c__))
6891            else:
6892                diag_T = numpy.matrix(numpy.diag(self.__tvec_a__))
6893            diag_S = numpy.matrix(
6894                numpy.diag(self.data_sstate.getStateData(*self.elas_var_col))
6895            )
6896
6897            # Calculate concentration responses to changes in T
6898            # (Cs*es + Im) * diag_S.I * reordered_zero_I * diag_T
6899            self.mca_rct_conc = numpy.dot(
6900                numpy.dot(
6901                    numpy.dot(
6902                        numpy.dot(reordered_cc_conc, self.elas_var) + Im,
6903                        numpy.linalg.inv(diag_S),
6904                    ),
6905                    reordered_zero_I,
6906                ),
6907                diag_T,
6908            )
6909
6910            # Calculate flux responses to changes in T
6911            # CJ*es * diag_S.I * reordered_zero_I * diag_T
6912            self.mca_rct_flux = numpy.dot(
6913                numpy.dot(
6914                    numpy.dot(
6915                        numpy.dot(reordered_cc_flux, self.elas_var),
6916                        numpy.linalg.inv(diag_S),
6917                    ),
6918                    reordered_zero_I,
6919                ),
6920                diag_T,
6921            )
6922        else:
6923            # Calculate concentration responses to changes in T
6924            # (Cs*es + Im) * reordered_zero_I
6925            self.mca_rct_conc = numpy.dot(
6926                numpy.dot(reordered_cc_conc, self.elas_var_u) + Im, reordered_zero_I
6927            )
6928
6929            # Calculate flux responses to changes in T
6930            # CJ*es * reordered_zero_I
6931            self.mca_rct_flux = numpy.dot(
6932                numpy.dot(reordered_cc_flux, self.elas_var_u), reordered_zero_I
6933            )
6934
6935        # Cleanup
6936        del reordered_cc_conc
6937        del reordered_cc_flux
6938
6939        # We simply prepend 'T_' to the name of the dependent species to prevent
6940        # confusion with the real species. This is a parameter, not variable.
6941        moiety_sum_names = [
6942            'T_{0}'.format(dependent_species)
6943            for dependent_species in self.Consmatrix.row
6944        ]
6945
6946        self.__structural__.MatrixFloatFix(self.mca_rct_conc)
6947        self.mca_rct_conc_row = self.elas_var_col
6948        self.mca_rct_conc_col = moiety_sum_names
6949        self.__structural__.MatrixFloatFix(self.mca_rct_flux)
6950        self.mca_rct_flux_row = self.elas_var_row
6951        self.mca_rct_flux_col = moiety_sum_names
6952
6953        # Augment the existing m.rc object (this could be more efficient)
6954
6955        # Reordering as a matter of principle: too many burnt fingers
6956        vstacked_col = list(self.elas_var_row) + list(self.elas_var_col)
6957        vstacked = numpy.vstack((self.mca_rct_flux, self.mca_rct_conc))
6958        reordered_vstack = numpy.zeros(vstacked.shape, 'd')
6959        for row_index in range(len(self.rc.row)):
6960            reordered_vstack[row_index, :] = vstacked[
6961                list(vstacked_col).index(self.rc.row[row_index]), :
6962            ]
6963        hstacked = numpy.hstack((self.rc.matrix, vstacked))
6964
6965        self.rc = BagOfStuff(hstacked, self.rc.row, self.rc.col + moiety_sum_names)
6966        self.rc.load()
6967        if self.__settings__['mode_mca_scaled'] == 1:
6968            self.rc.scaled = True
6969        else:
6970            self.rc.scaled = False
6971
6972    def EvalEigen(self):
6973        """
6974        EvalEigen()
6975
6976        Calculate the eigenvalues or vectors of the unscaled Jacobian matrix and thereby
6977        analyse the stability of a system
6978
6979        Arguments:
6980        None
6981
6982        """
6983        Nr = copy.copy(self.__nrmatrix__)
6984
6985        # sort E to match K and L
6986        Es = numpy.zeros((self.elas_var_u.shape), 'd')
6987        # print e2
6988
6989        for x in range(0, len(self.nmatrix_col)):
6990            for y in range(0, len(self.lmatrix_row)):
6991                Es[x, y] = self.elas_var_u[self.nmatrix_col[x], self.lmatrix_row[y]]
6992        if self.__HAS_MOIETY_CONSERVATION__ == True:
6993            jac1 = numpy.dot(Nr, Es)
6994            jacobian = numpy.dot(jac1, copy.copy(self.__lmatrix__))
6995        else:
6996            jacobian = numpy.dot(Nr, Es)
6997        Si = []
6998        for x in range(0, len(self.lmatrix_col)):
6999            Si.append(self.__species__[self.lmatrix_col[x]])
7000
7001            self.jacobian = tuple(jacobian)
7002            self.jacobian_row = tuple(Si)
7003            self.jacobian_col = tuple(Si)
7004            if self.__settings__['mode_eigen_output']:
7005                # returns eigenvalues as well as left and right eigenvectors
7006                eigenval, self.eigen_vecleft, self.eigen_vecright = scipy.linalg.eig(
7007                    jacobian, numpy.identity(jacobian.shape[0], 'd'), left=1, right=1
7008                )
7009                if self.__settings__['display_debug'] == 1:
7010                    print('\nEigenvalues')
7011                    print(eigenval)
7012                    print('\nLeft Eigenvector')
7013                    print(self.eigen_vecleft)
7014                    print('\nRight Eigenvector')
7015                    print(self.eigen_vecright)
7016            else:
7017                # returns eigenvalues
7018                eigenval = scipy.linalg.eigvals(jacobian)
7019                if self.__settings__['display_debug'] == 1:
7020                    print('\nEigenvalues')
7021                    print(eigenval)
7022
7023        self.eigen_values = eigenval
7024        # self.eigen_order = tuple(eigenorder)
7025
7026        for x in range(len(self.eigen_values)):
7027            setattr(self, 'lambda' + str(x + 1), self.eigen_values[x])
7028
7029        if self.__settings__['display_debug'] == 1:
7030            print(
7031                '\nEigenvalues attached as lambda1 ... lambda'
7032                + repr(len(eigenorder))
7033                + '\n'
7034            )
7035
7036    def showEigen(self, File=None):
7037        """
7038        showEigen(File=None)
7039
7040        Print the eigenvalues and stability analysis of a system generated with EvalEigen()
7041        to the screen or file.
7042
7043        Arguments:
7044
7045        File [default=None]: an open, writable Python file object
7046
7047        """
7048        eigenstats = ''
7049        eigenstats += (
7050            'Max real part: '
7051            + self.__settings__['mode_number_format'] % max(self.eigen_values.real)
7052            + '\n'
7053        )
7054        eigenstats += (
7055            'Min real part: '
7056            + self.__settings__['mode_number_format'] % min(self.eigen_values.real)
7057            + '\n'
7058        )
7059        eigenstats += (
7060            'Max absolute imaginary part: '
7061            + self.__settings__['mode_number_format'] % max(abs(self.eigen_values.imag))
7062            + '\n'
7063        )
7064        eigenstats += (
7065            'Min absolute imaginary part: '
7066            + self.__settings__['mode_number_format'] % min(abs(self.eigen_values.imag))
7067            + '\n'
7068        )
7069        eigenstats += (
7070            'Stiffness: '
7071            + self.__settings__['mode_number_format']
7072            % (max(abs(self.eigen_values.real)) / min(abs(self.eigen_values.real)))
7073            + '\n'
7074        )
7075
7076        pure_real = 0
7077        pure_imag = 0
7078        negcplx = 0
7079        pure_cplx = 0
7080        pure_zero = 0
7081        pos_real = 0
7082        neg_real = 0
7083        for x in self.eigen_values:
7084            if x.real != 0.0 and x.imag == 0.0:
7085                pure_real += 1
7086            if x.real == 0.0 and x.imag != 0.0:
7087                pure_imag += 1
7088            if x.real == 0.0 and x.imag == 0.0:
7089                pure_zero += 1
7090            if x.real > 0.0:
7091                pos_real += 1
7092            if x.real < 0.0:
7093                neg_real += 1
7094            if x.real < 0.0 and x.imag != 0.0:
7095                negcplx += 1
7096
7097        eigenstats += '\n## Stability'
7098        eiglen = len(self.eigen_values)
7099        if neg_real == eiglen:
7100            eigenstats += ' --> Stable state'
7101        elif pure_zero > 0:
7102            eigenstats += ' --> Undetermined'
7103        elif pos_real == eiglen:
7104            eigenstats += ' --> Unstable state'
7105
7106        eigenstats += '\nPurely real: ' + repr(pure_real)
7107        eigenstats += '\nPurely imaginary: ' + repr(pure_imag)
7108        eigenstats += '\nZero: ' + repr(pure_zero)
7109        eigenstats += '\nPositive real part: ' + repr(pos_real)
7110        eigenstats += '\nNegative real part: ' + repr(neg_real)
7111
7112        if File != None:
7113            # assert type(File) == file, 'showEigen() needs an open file object'
7114            print('\nEigen values')
7115            File.write('\n## Eigen values\n')
7116            scipy.io.write_array(File, self.eigen_values, precision=2, keep_open=1)
7117            print('\nEigen statistics')
7118            File.write('\n## Eigen statistics\n')
7119            File.write(eigenstats)
7120            if self.__settings__['mode_eigen_output']:
7121                print('\nLeft eigen vector')
7122                File.write('\n## Left eigen vector\n')
7123                scipy.io.write_array(File, self.eigen_vecleft, precision=2, keep_open=1)
7124                print('\nRight eigen vector')
7125                File.write('\n## Right eigen vector\n')
7126                scipy.io.write_array(
7127                    File, self.eigen_vecright, precision=2, keep_open=1
7128                )
7129        else:
7130            print('\nEigen values')
7131            print(self.eigen_values)
7132            print('\nEigen statistics')
7133            print(eigenstats)
7134            if self.__settings__['mode_eigen_output']:
7135                print('\nLeft eigen vector')
7136                print(self.eigen_vecleft)
7137                print('\nRight eigen vector')
7138                print(self.eigen_vecright)
7139
7140    # Utility functions
7141    # new generation metafunctions
7142    ##  def doLoad(self,stoich_load=0):
7143    ##  """
7144    ##  doLoad(stoich_load=0)
7145
7146    ##  Load and instantiate a PySCeS model so that it can be used for further analyses.
7147
7148    ##  Calls model loading subroutines:
7149    ##  Stoichiometry_Analyse() [override=0,load=stoich_load]
7150    ##  InitialiseModel()
7151
7152    ##  Arguments:
7153
7154    ##  stoich_load [default=0]: try to load a stoichiometry saved with Stoichiometry_Save_Serial()
7155
7156    ##  """
7157    ##  self.InitialiseInputFile()
7158    ##  assert self.__parseOK, '\nError in input file, parsing could not complete'
7159    ##  self.Stoichiometry_Analyse(override=0,load=stoich_load)
7160    ##  # add new Style functions to model
7161    ##  self.InitialiseFunctions()
7162    ##  self.InitialiseCompartments()
7163    ##  self.InitialiseRules()
7164    ##  self.InitialiseEvents()
7165    ##  self.InitialiseOldFunctions() # TODO replace this with initialisation functions
7166    ##  self.InitialiseModel()
7167    ##  self.InitialiseRuleChecks()
7168
7169    def doSim(self, end=10.0, points=21):
7170        """
7171        doSim(end=10.0,points=20.0)
7172
7173        Run a time simulation from t=0 to t=sim_end with sim_points.
7174
7175        Calls:
7176        Simulate()
7177
7178        Arguments:
7179
7180        end [default=10.0]: simulation end time
7181        points [default=20.0]: number of points in the simulation
7182
7183        """
7184        self.sim_end = end
7185        self.sim_points = points
7186        self.Simulate()
7187
7188    def doSimPlot(
7189        self, end=10.0, points=21, plot='species', fmt='lines', filename=None
7190    ):
7191        """
7192        Run a time simulation from t=0 to t=sim_end with sim_points and plot the results.
7193        The required output data and format can be set:
7194
7195        - *end* the end time (default=10.0)
7196        - *points* the number of points in the simulation (default=20.0)
7197        - *plot* (default='species') select output data
7198
7199         - 'species'
7200         - 'rates'
7201         - 'all' both species and rates
7202
7203        - *fmt* plot format, UPI backend dependent (default='') or the *CommonStyle* 'lines' or 'points'.
7204        - *filename* if not None (default) then the plot is exported as *filename*.png
7205
7206        Calls:
7207        - **Simulate()**
7208        - **SimPlot()**
7209        """
7210        self.sim_end = end
7211        self.sim_points = points
7212        self.Simulate()
7213        self.SimPlot(plot=plot, format=fmt, filename=filename)
7214
7215    def exportSimAsSedML(
7216        self,
7217        output='files',
7218        return_sed=False,
7219        vc_given='PySCeS',
7220        vc_family='Software',
7221        vc_email='',
7222        vc_org='pysces.sourceforge.net',
7223    ):
7224        """
7225        Exports the current simulation as SED-ML in various ways it creates and stores the SED-ML files in a folder
7226        generated from the model name.
7227
7228         - *output* [default='files'] the SED-ML export type can be one or more comma separated e.g. 'files,combine'
7229         - *files* export the plain SBML and SEDML XML files
7230         - *archive* export as a SED-ML archive *<file>.sedx* containing the SBML and SEDML xml files
7231         - *combine* export as a COMBINE archive *<file>.omex* containing the SBML, SEDML, manifest (XML) and metadata (RDF)
7232           - *vc_given* [default='PySCeS']
7233           - *vc_family* [default='Software']
7234           - *vc_email* [default='bgoli@users.sourceforge.net']
7235           - *vc_org* [default='<pysces.sourceforge.net>']
7236
7237        """
7238        sedname = self.ModelFile.replace('.psc', '')
7239
7240        sedout = os.path.join(OUTPUT_DIR, 'sedout')
7241        if not os.path.exists(sedout):
7242            os.makedirs(sedout)
7243        S = SED.SED(sedname + '_sed', sedout)
7244        S.addModel(sedname, self)
7245        S.addSimulation(
7246            'sim0',
7247            self.sim_start,
7248            self.sim_end,
7249            self.sim_points,
7250            self.__SIMPLOT_OUT__,
7251            initial=None,
7252            algorithm='KISAO:0000019',
7253        )
7254        S.addTask('task0', 'sim0', sedname)
7255        S.addTaskDataGenerators('task0')
7256        S.addTaskPlot('task0')
7257
7258        output = [s_.strip() for s_ in output.split(',')]
7259        for s_ in output:
7260            if s_ == 'files':
7261                S.writeSedScript()
7262                S.writeSedXML()
7263            elif s_ == 'archive':
7264                S.writeSedXArchive()
7265            elif s_ == 'combine':
7266                S.writeCOMBINEArchive(
7267                    vc_given=vc_given,
7268                    vc_family=vc_family,
7269                    vc_email=vc_email,
7270                    vc_org=vc_org,
7271                )
7272        S._SED_CURRENT_ = False
7273        if return_sed:
7274            return S
7275        else:
7276            del S
7277
7278    def doSimPerturb(self, pl, end):
7279        """
7280        **Deprecated**: use events instead
7281        """
7282        pass
7283
7284    def doState(self):
7285        """
7286        doState()
7287
7288        Calculate the steady-state solution of the system.
7289
7290        Calls:
7291        State()
7292
7293        Arguments:
7294        None
7295
7296        """
7297        self.State()
7298
7299    def doStateShow(self):
7300        """
7301        doStateShow()
7302
7303        Calculate the steady-state solution of a system and show the results.
7304
7305        Calls:
7306        State()
7307        showState()
7308
7309        Arguments:
7310        None
7311
7312        """
7313        self.State()
7314        assert (
7315            self.__StateOK__ == True
7316        ), '\n\nINFO: Invalid steady state: run mod.doState() and check for errors'
7317        self.showState()
7318
7319    def doElas(self):
7320        """
7321        doElas()
7322
7323        Calculate the model elasticities, this method automatically calculates a steady state.
7324
7325        Calls:
7326        State()
7327        EvalEvar()
7328        EvalEpar()
7329
7330        Arguments:
7331        None
7332
7333        """
7334        self.State()
7335        assert (
7336            self.__StateOK__ == True
7337        ), '\n\nINFO: Invalid steady state: run mod.doState() and check for errors'
7338        self.EvalEvar()
7339        self.EvalEpar()
7340
7341    def doEigen(self):
7342        """
7343        doEigen()
7344
7345        Calculate the eigenvalues, automatically performs a steady state and elasticity analysis.
7346
7347        Calls:
7348        State()
7349        EvalEvar()
7350        Evaleigen()
7351
7352        Arguments:
7353        None
7354
7355        """
7356        self.State()
7357        assert (
7358            self.__StateOK__ == True
7359        ), '\n\nINFO: Invalid steady state: run mod.doState() and check for errors'
7360        self.__settings__["elas_evar_upsymb"] = 1
7361        self.EvalEvar()
7362        self.__settings__["elas_evar_upsymb"] = 1
7363        self.EvalEigen()
7364
7365    def doEigenShow(self):
7366        """
7367        doEigenShow()
7368
7369        Calculate the eigenvalues, automatically performs a steady state and elasticity analysis
7370        and displays the results.
7371
7372        Calls:
7373        doEigen()
7374        showEigen()
7375
7376        Arguments:
7377        None
7378
7379        """
7380        self.doEigen()
7381        self.showEigen()
7382
7383    def doEigenMca(self):
7384        """
7385        doEigenMca()
7386
7387        Calculate a full Control Analysis and eigenvalues, automatically performs a steady state, elasticity, control analysis.
7388
7389        Calls:
7390        State()
7391        EvalEvar()
7392        EvalCC()
7393        Evaleigen()
7394
7395        Arguments:
7396        None
7397
7398        """
7399        self.State()
7400        assert (
7401            self.__StateOK__ == True
7402        ), '\n\nINFO: Invalid steady state: run mod.doState() and check for errors'
7403        self.EvalEvar()
7404        self.EvalCC()
7405        self.EvalEigen()
7406
7407    def doMca(self):
7408        """
7409        doMca()
7410
7411        Perform a complete Metabolic Control Analysis on the model, automatically calculates a steady state.
7412
7413        Calls:
7414        State()
7415        EvalEvar()
7416        EvalEpar()
7417        EvalCC()
7418
7419        Arguments:
7420        None
7421
7422        """
7423        self.State()
7424        assert (
7425            self.__StateOK__ == True
7426        ), '\n\nINFO: Invalid steady state run: mod.doState() and check for errors'
7427        self.EvalEvar()
7428        self.EvalEpar()
7429        self.EvalCC()
7430
7431    def doMcaRC(self):
7432        """
7433        doMca()
7434
7435        Perform a complete Metabolic Control Analysis on the model, automatically calculates a steady state.
7436
7437        Calls:
7438        State()
7439        EvalEvar()
7440        EvalEpar()
7441        EvalCC()
7442        EvalRC()
7443
7444        Arguments:
7445        None
7446
7447        """
7448        self.State()
7449        assert (
7450            self.__StateOK__ == True
7451        ), '\n\nINFO: Invalid steady state run: mod.doState() and check for errors'
7452        self.EvalEvar()
7453        self.EvalEpar()
7454        self.EvalCC()
7455        self.EvalRC()
7456
7457    def doMcaRCT(self):
7458        """
7459        doMcaRCT()
7460
7461        Perform a complete Metabolic Control Analysis on the model, automatically calculates a steady state.
7462
7463        In additional, response coefficients to the sums of moiety-conserved cycles are calculated.
7464
7465        Calls:
7466        State()
7467        EvalEvar()
7468        EvalEpar()
7469        EvalCC()
7470        EvalRC()
7471        EvalRCT()
7472
7473        Arguments:
7474        None
7475
7476        """
7477        self.State()
7478        assert (
7479            self.__StateOK__ == True
7480        ), '\n\nINFO: Invalid steady state run: mod.doState() and check for errors'
7481        self.EvalEvar()
7482        self.EvalEpar()
7483        self.EvalCC()
7484        self.EvalRC()
7485        if self.__HAS_MOIETY_CONSERVATION__:
7486            self.EvalRCT()
7487        else:
7488            print('No moiety conservation detected, reverting to simple doMcaRC().')
7489
7490    # show/save function prototypes
7491    def showSpecies(self, File=None):
7492        """
7493        showSpecies(File=None)
7494
7495        Prints the current value of the model's variable species (mod.X) to screen or file.
7496
7497        Arguments:
7498
7499        File [default=None]: an open, writable Python file object
7500
7501        """
7502        out_list = []
7503
7504        print('\nSpecies values')
7505        out_list.append('\n## species values\n')
7506        for x in range(len(self.__species__)):
7507            if File == None:
7508                ##  print self.__species__[x] + ' = ' +  self.__settings__['mode_number_format'] % eval('self.' + self.__species__[x])
7509                print(
7510                    self.__species__[x]
7511                    + ' = '
7512                    + self.__settings__['mode_number_format']
7513                    % getattr(self, self.__species__[x])
7514                )
7515            else:
7516                ##  out_list.append(self.__species__[x] + ' = ' +  self.__settings__['mode_number_format'] % eval('self.' + self.__species__[x]) + '\n')
7517                out_list.append(
7518                    self.__species__[x]
7519                    + ' = '
7520                    + self.__settings__['mode_number_format']
7521                    % getattr(self, self.__species__[x])
7522                    + '\n'
7523                )
7524        if File != None:
7525            for x in out_list:
7526                File.write(x)
7527
7528    def showSpeciesI(self, File=None):
7529        """
7530        showSpeciesI(File=None)
7531
7532        Prints the current value of the model's variable species initial values (mod.X_init) to screen or file.
7533
7534        Arguments:
7535
7536        File [default=None]: an open, writable Python file object
7537
7538        """
7539        out_list = []
7540
7541        print('\nSpecies initial values')
7542        out_list.append('\n## species initial values\n')
7543        for x in range(len(self.__species__)):
7544            if File == None:
7545                print(
7546                    self.__species__[x]
7547                    + '_init = '
7548                    + self.__settings__['mode_number_format']
7549                    % getattr(self, self.__species__[x] + '_init')
7550                )
7551            else:
7552                out_list.append(
7553                    self.__species__[x]
7554                    + ' = '
7555                    + self.__settings__['mode_number_format']
7556                    % getattr(self, self.__species__[x] + '_init')
7557                    + '\n'
7558                )
7559        if File != None:
7560            for x in out_list:
7561                File.write(x)
7562
7563    def showSpeciesFixed(self, File=None):
7564        """
7565        showSpeciesFixed(File=None)
7566
7567        Prints the current value of the model's fixed species values (mod.X) to screen or file.
7568
7569        Arguments:
7570
7571        File [default=None]: an open, writable Python file object
7572
7573        """
7574        out_list = []
7575
7576        print('\nFixed species')
7577        out_list.append('\n## fixed species\n')
7578        for x in range(len(self.__fixed_species__)):
7579            if File == None:
7580                print(
7581                    self.__fixed_species__[x]
7582                    + ' = '
7583                    + self.__settings__['mode_number_format']
7584                    % getattr(self, self.__fixed_species__[x])
7585                )
7586            else:
7587                out_list.append(
7588                    self.__fixed_species__[x]
7589                    + ' = '
7590                    + self.__settings__['mode_number_format']
7591                    % getattr(self, self.__fixed_species__[x])
7592                    + '\n'
7593                )
7594        if File != None:
7595            for x in out_list:
7596                File.write(x)
7597
7598    def showPar(self, File=None):
7599        """
7600        showPar(File=None)
7601
7602        Prints the current value of the model's parameter values (mod.P) to screen or file.
7603
7604        Arguments:
7605
7606        File [default=None]: an open, writable Python file object
7607
7608        """
7609        out_list = []
7610
7611        print('\nParameters')
7612        out_list.append('\n## parameters\n')
7613        for x in range(len(self.__parameters__)):
7614            if File == None:
7615                print(
7616                    self.__parameters__[x]
7617                    + ' = '
7618                    + self.__settings__['mode_number_format']
7619                    % getattr(self, self.__parameters__[x])
7620                )
7621            else:
7622                out_list.append(
7623                    self.__parameters__[x]
7624                    + ' = '
7625                    + self.__settings__['mode_number_format']
7626                    % getattr(self, self.__parameters__[x])
7627                    + '\n'
7628                )
7629        if File != None:
7630            for x in out_list:
7631                File.write(x)
7632
7633    def showModifiers(self, File=None):
7634        """
7635        showModifiers(File=None)
7636
7637        Prints the current value of the model's modifiers per reaction to screen or file.
7638
7639        Arguments:
7640
7641        File [default=None]: an open, writable Python file object
7642
7643        """
7644        noMod = []
7645        out_list = []
7646
7647        print('\nModifiers per reaction:')
7648        out_list.append('\n## modifiers per reaction\n')
7649        for reac in self.__modifiers__:
7650            rstr = ''
7651            if len(reac[1]) == 1:
7652                if File == None:
7653                    print(reac[0] + ' has modifier:', end=' ')
7654                rstr = rstr + reac[0] + ' has modifier: '
7655                for x in reac[1]:
7656                    if File == None:
7657                        print(x, end=' ')
7658                    rstr = rstr + x + ' '
7659                if File == None:
7660                    print(' ')
7661                rstr += '\n'
7662            elif len(reac[1]) > 1:
7663                if File == None:
7664                    print(reac[0] + ' has modifiers: ', end=' ')
7665                rstr = rstr + reac[0] + ' has modifiers: '
7666                for x in reac[1]:
7667                    if File == None:
7668                        print(x, end=' ')
7669                    rstr = rstr + x + ' '
7670                if File == None:
7671                    print(' ')
7672                rstr += '\n'
7673            else:
7674                noMod.append(reac[0])
7675
7676            out_list.append(rstr)
7677        if len(noMod) > 0:
7678            print('\nReactions with no modifiers:')
7679            out_list.append('\n## reactions with no modifiers\n')
7680            cntr = 0
7681            rstr = ''
7682            for n in range(len(noMod)):
7683                cntr += 1
7684                if cntr > 6:
7685                    if File == None:
7686                        print(' ')
7687                    rstr += '\n'
7688                    cntr = 0
7689                if File == None:
7690                    print(noMod[n], end=' ')
7691                rstr = rstr + noMod[n] + ' '
7692            if File == None:
7693                print(' ')
7694            rstr += '\n'
7695            out_list.append(rstr)
7696
7697            if File != None:
7698                for x in out_list:
7699                    File.write(x)
7700
7701    def showState(self, File=None):
7702        """
7703        showState(File=None)
7704
7705        Prints the result of the last steady-state analyses. Both steady-state flux's and species concentrations are shown.
7706
7707        Arguments:
7708
7709        File [default=None]: an open, writable Python file object
7710
7711        """
7712        out_list = []
7713
7714        print('\nSteady-state species concentrations')
7715        out_list.append('\n## Current steady-state species concentrations\n')
7716        if self.__StateOK__:
7717            for x in range(len(self.state_species)):
7718                if File == None:
7719                    print(
7720                        self.__species__[x]
7721                        + '_ss = '
7722                        + self.__settings__['mode_number_format']
7723                        % self.state_species[x]
7724                    )
7725                else:
7726                    out_list.append(
7727                        self.__species__[x]
7728                        + '_ss = '
7729                        + self.__settings__['mode_number_format']
7730                        % self.state_species[x]
7731                        + '\n'
7732                    )
7733        else:
7734            print('No valid steady state found')
7735            out_list.append('No valid steady state found.\n')
7736
7737        print('\nSteady-state fluxes')
7738        out_list.append('\n## Steady-state fluxes\n')
7739        if self.__StateOK__:
7740            for x in range(len(self.state_flux)):
7741                if File == None:
7742                    print(
7743                        'J_'
7744                        + self.__reactions__[x]
7745                        + ' = '
7746                        + self.__settings__['mode_number_format'] % self.state_flux[x]
7747                    )
7748                else:
7749                    out_list.append(
7750                        'J_'
7751                        + self.__reactions__[x]
7752                        + ' = '
7753                        + self.__settings__['mode_number_format'] % self.state_flux[x]
7754                        + '\n'
7755                    )
7756        else:
7757            print('No valid steady state found')
7758            out_list.append('No valid steady state found.\n')
7759
7760        if File != None:
7761            for x in out_list:
7762                File.write(x)
7763
7764    def showRate(self, File=None):
7765        """
7766        Prints the current rates of all the reactions using the current parameter values and species concentrations
7767
7768        - *File* an open, writable Python file object (default=None)
7769
7770        """
7771        Vtemp = [r() for r in getattr(self, self.__reactions__[r])]
7772        outrate = ''
7773        for x in range(len(self.__reactions__)):
7774            outrate += (
7775                self.__reactions__[x]
7776                + ' = '
7777                + self.__settings__['mode_number_format'] % Vtemp[x]
7778                + '\n'
7779            )
7780        del s, Vtemp
7781
7782        if File != None:
7783            print('\nReaction rates')
7784            File.write('\n##Reaction rates\n')
7785            File.write(outrate)
7786        else:
7787            print('\nReaction rates')
7788            print(outrate)
7789
7790    def showRateEq(self, File=None):
7791        """
7792        showRateEq(File=None)
7793
7794        Prints the reaction stoichiometry and rate equations to screen or File.
7795
7796        Arguments:
7797
7798        File [default=None]: an open, writable Python file object
7799        """
7800        out_list = []
7801
7802        tnform = self.__settings__['mode_number_format']
7803
7804        self.__settings__['mode_number_format'] = '%2.5f'
7805
7806        print('\nReaction stoichiometry and rate equations')
7807        out_list.append('\n## Reaction stoichiometry and rate equations\n')
7808
7809        # writes these out in a better order
7810        for key in self.Kmatrix.row:
7811            if File == None:
7812                print(key + ':')
7813            else:
7814                out_list.append(key + ':\n')
7815            reagL = []
7816            reagR = []
7817            for reagent in self.__nDict__[key]['Reagents']:
7818                if self.__nDict__[key]['Reagents'][reagent] > 0:
7819                    if self.__nDict__[key]['Reagents'][reagent] == 1.0:
7820                        reagR.append(reagent.replace('self.', ''))
7821                    else:
7822                        reagR.append(
7823                            '{'
7824                            + self.__settings__['mode_number_format']
7825                            % abs(self.__nDict__[key]['Reagents'][reagent])
7826                            + '}'
7827                            + reagent.replace('self.', '')
7828                        )
7829                elif self.__nDict__[key]['Reagents'][reagent] < 0:
7830                    if self.__nDict__[key]['Reagents'][reagent] == -1.0:
7831                        reagL.append(reagent.replace('self.', ''))
7832                    else:
7833                        reagL.append(
7834                            '{'
7835                            + self.__settings__['mode_number_format']
7836                            % abs(self.__nDict__[key]['Reagents'][reagent])
7837                            + '}'
7838                            + reagent.replace('self.', '')
7839                        )
7840
7841            substring = ''
7842            count = 0
7843            for x in reagL:
7844                if count != 0:
7845                    substring += ' + '
7846                substring += x.replace(' ', '')
7847                count += 1
7848            prodstring = ''
7849            count = 0
7850            for x in reagR:
7851                if count != 0:
7852                    prodstring += ' + '
7853                prodstring += x.replace(' ', '')
7854                count += 1
7855
7856            if self.__nDict__[key]['Type'] == 'Rever':
7857                symbol = ' = '
7858            else:
7859                symbol = ' > '
7860            if File == None:
7861                print('\t' + substring + symbol + prodstring)
7862                print('\t' + self.__nDict__[key]['RateEq'].replace('self.', ''))
7863            else:
7864                out_list.append('\t' + substring + symbol + prodstring + '\n')
7865                out_list.append(
7866                    '\t' + self.__nDict__[key]['RateEq'].replace('self.', '') + '\n\n'
7867                )
7868        if len(list(self.__rules__.keys())) > 0:
7869            out_list.append('\n# Assignment rules\n')
7870            for ass in self.__rules__:
7871                out_list.append(
7872                    '!F {} = {}\n'.format(
7873                        self.__rules__[ass]['name'], self.__rules__[ass]['formula']
7874                    )
7875                )
7876            out_list.append('\n')
7877        if File != None:
7878            for x in out_list:
7879                File.write(x)
7880        self.__settings__['mode_number_format'] = tnform
7881
7882    def showODE(self, File=None, fmt='%2.3f'):
7883        """
7884        showODE(File=None,fmt='%2.3f')
7885
7886        Print a representation of the full set of ODE's generated by PySCeS to screen or file.
7887
7888        Arguments:
7889
7890        File [default=None]: an open, writable Python file object
7891        fmt [default='%2.3f']: output number format
7892
7893        """
7894        maxmetlen = 0
7895        for x in self.__species__:
7896            if len(x) > maxmetlen:
7897                maxmetlen = len(x)
7898
7899        maxreaclen = 0
7900        for x in self.__reactions__:
7901            if len(x) > maxreaclen:
7902                maxreaclen = len(x)
7903        odes = '\n## ODE\'s (unreduced)\n'
7904        for x in range(self.__nmatrix__.shape[0]):
7905            odes += (
7906                'd'
7907                + self.__species__[x]
7908                + (maxmetlen - len(self.__species__[x])) * ' '
7909                + '|'
7910            )
7911            beginline = 0
7912            for y in range(self.__nmatrix__.shape[1]):
7913                if abs(self.__nmatrix__[x, y]) > 0.0:
7914                    if self.__nmatrix__[x, y] > 0.0:
7915                        if beginline == 0:
7916                            odes += (
7917                                '  '
7918                                + fmt % abs(self.__nmatrix__[x, y])
7919                                + '*'
7920                                + self.__reactions__[y]
7921                                + (maxreaclen - len(self.__reactions__[y])) * ' '
7922                            )
7923                            beginline = 1
7924                        else:
7925                            odes += (
7926                                ' + '
7927                                + fmt % abs(self.__nmatrix__[x, y])
7928                                + '*'
7929                                + self.__reactions__[y]
7930                                + (maxreaclen - len(self.__reactions__[y])) * ' '
7931                            )
7932                    else:
7933                        if beginline == 0:
7934                            odes += (
7935                                ' -'
7936                                + fmt % abs(self.__nmatrix__[x, y])
7937                                + '*'
7938                                + self.__reactions__[y]
7939                                + (maxreaclen - len(self.__reactions__[y])) * ' '
7940                            )
7941                        else:
7942                            odes += (
7943                                ' - '
7944                                + fmt % abs(self.__nmatrix__[x, y])
7945                                + '*'
7946                                + self.__reactions__[y]
7947                                + (maxreaclen - len(self.__reactions__[y])) * ' '
7948                            )
7949                        beginline = 1
7950            odes += '\n'
7951
7952        if self.__HAS_RATE_RULES__:
7953            odes += "\n## Rate Rules:\n"
7954            for rule in self.__rate_rules__:
7955                odes += "d{} | {}\n".format(
7956                    rule, self.__rules__[rule]['formula'].replace('()', ''),
7957                )
7958            odes += '\n'
7959
7960        if File != None:
7961            print('\nODE\'s (unreduced)\n')
7962            File.write(odes)
7963        else:
7964            print(odes)
7965
7966    def showODEr(self, File=None, fmt='%2.3f'):
7967        """
7968        showODEr(File=None,fmt='%2.3f')
7969
7970        Print a representation of the reduced set of ODE's generated by PySCeS to screen or file.
7971
7972        Arguments:
7973
7974        File [default=None]: an open, writable Python file object
7975        fmt [default='%2.3f']: output number format
7976
7977        """
7978        maxmetlen = 0
7979        for x in self.__species__:
7980            if len(x) > maxmetlen:
7981                maxmetlen = len(x)
7982        maxreaclen = 0
7983        for x in self.__reactions__:
7984            if len(x) > maxreaclen:
7985                maxreaclen = len(x)
7986
7987        odes = '\n## ODE\'s (reduced)\n'
7988        for x in range(self.nrmatrix.shape[0]):
7989            odes += (
7990                'd'
7991                + self.__species__[self.nrmatrix_row[x]]
7992                + (maxmetlen - len(self.__species__[self.nrmatrix_row[x]])) * ' '
7993                + '|'
7994            )
7995            beginline = 0
7996            for y in range(self.__nrmatrix__.shape[1]):
7997                if abs(self.__nrmatrix__[x, y]) > 0.0:
7998                    if self.__nrmatrix__[x, y] > 0.0:
7999                        if beginline == 0:
8000                            odes += (
8001                                '  '
8002                                + fmt % abs(self.__nrmatrix__[x, y])
8003                                + '*'
8004                                + self.__reactions__[self.nrmatrix_col[y]]
8005                                + (
8006                                    maxreaclen
8007                                    - len(self.__reactions__[self.nrmatrix_col[y]])
8008                                )
8009                                * ' '
8010                            )
8011                            beginline = 1
8012                        else:
8013                            odes += (
8014                                ' + '
8015                                + fmt % abs(self.__nrmatrix__[x, y])
8016                                + '*'
8017                                + self.__reactions__[self.nrmatrix_col[y]]
8018                                + (
8019                                    maxreaclen
8020                                    - len(self.__reactions__[self.nrmatrix_col[y]])
8021                                )
8022                                * ' '
8023                            )
8024                    else:
8025                        if beginline == 0:
8026                            odes += (
8027                                ' -'
8028                                + fmt % abs(self.__nrmatrix__[x, y])
8029                                + '*'
8030                                + self.__reactions__[self.nrmatrix_col[y]]
8031                                + (
8032                                    maxreaclen
8033                                    - len(self.__reactions__[self.nrmatrix_col[y]])
8034                                )
8035                                * ' '
8036                            )
8037                        else:
8038                            odes += (
8039                                ' - '
8040                                + fmt % abs(self.__nrmatrix__[x, y])
8041                                + '*'
8042                                + self.__reactions__[self.nrmatrix_col[y]]
8043                                + (
8044                                    maxreaclen
8045                                    - len(self.__reactions__[self.nrmatrix_col[y]])
8046                                )
8047                                * ' '
8048                            )
8049                        beginline = 1
8050            odes += '\n'
8051
8052        if self.__HAS_RATE_RULES__:
8053            odes += "\n## Rate Rules:\n"
8054            for rule in self.__rate_rules__:
8055                odes += "d{} | {}\n".format(
8056                    rule, self.__rules__[rule]['formula'].replace('()', ''),
8057                )
8058            odes += '\n'
8059
8060        if File != None:
8061            print('\nODE\'s (reduced)\n')
8062            File.write(odes)
8063            self.showConserved(File)
8064        else:
8065            print(odes)
8066            self.showConserved()
8067
8068    def showModel(self, filename=None, filepath=None, skipcheck=0):
8069        """
8070        showModel(filename=None,filepath=None,skipcheck=0)
8071
8072        The PySCeS 'save' command, prints the entire model to screen or File in a PSC format.
8073        (Currently this only applies to basic model attributes, ! functions are not saved).
8074
8075        Arguments:
8076
8077        filename [default=None]: the output PSC file
8078        filepath [default=None]: the output directory
8079        skipcheck [default=0]: skip check to see if the file exists (1) auto-averwrite
8080
8081        """
8082        if filepath == None:
8083            filepath = self.ModelDir
8084
8085        if filename == None:
8086            print('\nFixed species')
8087            if len(self.__fixed_species__) == 0:
8088                print('<none>')
8089            else:
8090                for x in self.__fixed_species__:
8091                    print(x, end=' ')
8092            print(' ')
8093
8094            self.showRateEq()
8095            self.showSpeciesI()
8096            self.showPar()
8097        else:
8098            # assert type(filename) == str, 'showModel() takes a string filename argument'
8099            FBuf = 0
8100            if type(filename) == str:
8101                # filename = str(filename)
8102                filename = chkpsc(filename)
8103                go = 0
8104                loop = 0
8105                if skipcheck != 0:
8106                    loop = 1
8107                    go = 1
8108                while loop == 0:
8109                    try:
8110                        filex = os.path.join(filepath, filename)
8111                        if os.path.isfile(filex):
8112                            inp = input(
8113                                '\nfile ' + filex + ' exists.\nOverwrite? ([y]/n) '
8114                            )
8115                        else:
8116                            raise IOError
8117                        if inp == 'y' or inp == '':
8118                            go = 1
8119                            loop = 1
8120                        elif inp == 'n':
8121                            filename = input(
8122                                '\nfile "'
8123                                + filename
8124                                + '" exists. Enter a new filename: '
8125                            )
8126                            go = 1
8127                            filex = os.path.join(filepath, filename)
8128                            filename = chkpsc(filename)
8129                        else:
8130                            print('\nInvalid input')
8131                    except IOError:
8132                        print('\nfile "' + filex + '" does not exist, proceeding')
8133                        loop = 1
8134                        go = 1
8135            else:
8136                print('\nI hope we have a filebuffer')
8137                if (
8138                    type(filename).__name__ == 'file'
8139                    or type(filename).__name__ == 'StringIO'
8140                    or type(filename).__name__ == 'TextIOWrapper'
8141                ):
8142                    go = 1
8143                    FBuf = 1
8144                    print('Seems like it')
8145                else:
8146                    go = 0
8147                    print('Are you sure you know what ur doing')
8148
8149            if go == 1:
8150                if not FBuf:
8151                    if filex[-4:] == '.psc':
8152                        pass
8153                    else:
8154                        print('Assuming extension is .psc')
8155                        filex += '.psc'
8156                    outFile = open(filex, 'w')
8157                else:
8158                    outFile = filename
8159
8160                # header =   '# \n'
8161                header = '# PySCeS (' + __version__ + ') model input file \n'
8162                # header += '# Copyright Brett G. Olivier, 2004 (bgoli at sun dot ac dot za)  \n'
8163                header += '# http://pysces.sourceforge.net/ \n'
8164                header += '# \n'
8165                header += '# Original input file: ' + self.ModelFile + '\n'
8166                header += (
8167                    '# This file generated: '
8168                    + time.strftime("%a, %d %b %Y %H:%M:%S")
8169                    + '\n\n'
8170                )
8171                outFile.write(header)
8172
8173                outFile.write('## Fixed species\n')
8174                if len(self.__fixed_species__) == 0:
8175                    outFile.write('#  <none>')
8176                else:
8177                    outFile.write('FIX: ')
8178                    for x in self.__fixed_species__:
8179                        outFile.write(x + ' ')
8180                outFile.write('\n')
8181                self.showRateEq(File=outFile)
8182                self.showSpeciesI(File=outFile)
8183                self.showPar(File=outFile)
8184                if (
8185                    type(filename) == str
8186                    or type(filename).__name__ == 'file'
8187                    or type(filename).__name__ == 'TextIOWrapper'
8188                ):
8189                    # don't close StringIO objects
8190                    outFile.close()
8191
8192    def showN(self, File=None, fmt='%2.3f'):
8193        """
8194        showN(File=None,fmt='%2.3f')
8195
8196        Print the stoichiometric matrix (N), including row and column labels to screen or File.
8197
8198        Arguments:
8199
8200        File [default=None]: an open, writable Python file object
8201        fmt [default='%2.3f']: output number format
8202        """
8203        km_trunc = 0
8204        km_trunc2 = 0
8205        km = ''
8206        rtr = []
8207        ctr = []
8208        for a in range(len(self.nmatrix_col)):
8209            if a == 0:
8210                km += 9 * ' '
8211            if len(self.__reactions__[self.nmatrix_col[a]]) > 7:
8212                km += self.__reactions__[self.nmatrix_col[a]][:6] + '* '
8213                km_trunc = 1
8214                ctr.append(a)
8215            else:
8216                km += self.__reactions__[a] + (9 - len(self.__reactions__[a]) - 1) * ' '
8217        for x in range(len(self.nmatrix_row)):
8218            if len(self.__species__[x]) > 6:
8219                km += '\n' + self.__species__[x][:5] + '*' + 1 * ' '
8220                km_trunc2 = 1
8221                rtr.append(x)
8222            else:
8223                km += (
8224                    '\n'
8225                    + self.__species__[x]
8226                    + (8 - len(self.__species__[x]) - 1) * ' '
8227                )
8228                # km += '\n' + self.__reactions__[self.nmatrix_row[x]] + 4*' '
8229            for y in range(len(self.nmatrix_col)):
8230                if y != 0:
8231                    km += 3 * ' '
8232                if self.__nmatrix__[x, y] >= 10.0:
8233                    km += ' ' + fmt % self.__nmatrix__[x, y]
8234                elif self.__nmatrix__[x, y] >= 0.0:
8235                    km += '  ' + fmt % self.__nmatrix__[x, y]
8236                else:
8237                    if abs(self.__nmatrix__[x, y]) >= 10.0:
8238                        km += fmt % self.__nmatrix__[x, y]
8239                    else:
8240                        km += ' ' + fmt % self.__nmatrix__[x, y]
8241        if km_trunc:
8242            km += '\n\n' + '* indicates truncated (col) reaction\n('
8243            for x in range(len(self.nmatrix_col)):
8244                try:
8245                    ctr.index(x)
8246                    km += self.__reactions__[x] + ', '
8247                except:
8248                    pass
8249            km += ')'
8250        if km_trunc2:
8251            km += '\n\n' + '* indicates truncated (row) species:\n('
8252            for x in range(len(self.nmatrix_row)):
8253                try:
8254                    rtr.index(x)
8255                    km += self.__species__[x] + ', '
8256                except:
8257                    pass
8258            km += ')'
8259
8260        if File != None:
8261            print('\nStoichiometric matrix (N)')
8262            File.write('\n\n## Stoichiometric matrix (N)\n')
8263            File.write(km)
8264            File.write('\n')
8265        else:
8266            print('\nStoichiometric matrix (N)')
8267            print(km)
8268
8269    def showNr(self, File=None, fmt='%2.3f'):
8270        """
8271        showNr(File=None,fmt='%2.3f')
8272
8273        Print the reduced stoichiometric matrix (Nr), including row and column labels to screen or File.
8274
8275        Arguments:
8276
8277        File [default=None]: an open, writable Python file object
8278        fmt [default='%2.3f']: output number format
8279        """
8280        km_trunc = 0
8281        km_trunc2 = 0
8282        km = ''
8283        ctr = []
8284        rtr = []
8285        for a in range(len(self.nrmatrix_col)):
8286            if a == 0:
8287                km += 9 * ' '
8288            if len(self.__reactions__[self.nrmatrix_col[a]]) > 7:
8289                km += self.__reactions__[self.nrmatrix_col[a]][:6] + '* '
8290                km_trunc = 1
8291                ctr.append(a)
8292            else:
8293                km += self.__reactions__[a] + (9 - len(self.__reactions__[a]) - 1) * ' '
8294        for x in range(len(self.nrmatrix_row)):
8295            if len(self.__species__[self.nrmatrix_row[x]]) > 6:
8296                km += '\n' + self.__species__[self.nrmatrix_row[x]][:5] + '*' + 1 * ' '
8297                km_trunc2 = 1
8298                rtr.append(x)
8299            else:
8300                km += (
8301                    '\n'
8302                    + self.__species__[self.nrmatrix_row[x]]
8303                    + (8 - len(self.__species__[self.nrmatrix_row[x]]) - 1) * ' '
8304                )
8305                # km += '\n' + self.__reactions__[self.nrmatrix_row[x]] + 4*' '
8306            for y in range(len(self.nrmatrix_col)):
8307                if y != 0:
8308                    km += 3 * ' '
8309                if self.__nrmatrix__[x, y] >= 10.0:
8310                    km += ' ' + fmt % self.__nrmatrix__[x, y]
8311                elif self.__nrmatrix__[x, y] >= 0.0:
8312                    km += '  ' + fmt % self.__nrmatrix__[x, y]
8313                else:
8314                    if abs(self.__nrmatrix__[x, y]) >= 10.0:
8315                        km += fmt % self.__nrmatrix__[x, y]
8316                    else:
8317                        km += ' ' + fmt % self.__nrmatrix__[x, y]
8318        if km_trunc:
8319            km += '\n\n' + '* indicates truncated (col) reaction:\n('
8320            for x in range(len(self.nrmatrix_col)):
8321                try:
8322                    ctr.index(x)
8323                    km += self.__reactions__[x] + ', '
8324                except:
8325                    pass
8326            km += ')'
8327        if km_trunc2:
8328            km += '\n\n' + '* indicates truncated (row) species:\n('
8329            for x in range(len(self.nrmatrix_row)):
8330                try:
8331                    rtr.index(x)
8332                    km += self.__species__[self.nrmatrix_row[x]] + ', '
8333                except:
8334                    pass
8335            km += ')'
8336        if File != None:
8337            print('\nReduced stoichiometric matrix (Nr)')
8338            File.write('\n\n## Reduced stoichiometric matrix (Nr)\n')
8339            File.write(km)
8340            File.write('\n')
8341        else:
8342            print('\nReduced stoichiometric matrix (Nr)')
8343            print(km)
8344
8345    def showK(self, File=None, fmt='%2.3f'):
8346        """
8347        showK(File=None,fmt='%2.3f')
8348
8349        Print the Kernel matrix (K), including row and column labels to screen or File.
8350
8351        Arguments:
8352
8353        File [default=None]: an open, writable Python file object
8354        fmt [default='%2.3f']: output number format
8355        """
8356        km_trunc = 0
8357        km_trunc2 = 0
8358        km = ''
8359        ctr = []
8360        rtr = []
8361        for a in range(len(self.kmatrix_col)):
8362            if a == 0:
8363                km += 8 * ' '
8364            if len(self.__reactions__[self.kmatrix_col[a]]) > 7:
8365                km += self.__reactions__[self.kmatrix_col[a]][:6] + '* '
8366                km_trunc = 1
8367                ctr.append(a)
8368            else:
8369                km += (
8370                    self.__reactions__[self.kmatrix_col[a]]
8371                    + (9 - len(self.__reactions__[self.kmatrix_col[a]]) - 1) * ' '
8372                )
8373
8374        for x in range(len(self.kmatrix_row)):
8375            if len(self.__reactions__[self.kmatrix_row[x]]) > 6:
8376                km += '\n' + self.__reactions__[self.kmatrix_row[x]][:5] + '*' + 1 * ' '
8377                km_trunc2 = 1
8378                rtr.append(x)
8379            else:
8380                km += (
8381                    '\n'
8382                    + self.__reactions__[self.kmatrix_row[x]]
8383                    + (8 - len(self.__reactions__[self.kmatrix_row[x]]) - 1) * ' '
8384                )
8385                # km += '\n' + self.__reactions__[self.kmatrix_row[x]] + 4*' '
8386            for y in range(len(self.kmatrix_col)):
8387                if y != 0:
8388                    km += 4 * ' '
8389                if abs(self.__kmatrix__[x, y]) == 0.0:
8390                    km += ' ' + fmt % abs(self.__kmatrix__[x, y])
8391                elif self.__kmatrix__[x, y] < 0.0:
8392                    km += fmt % self.__kmatrix__[x, y]
8393                else:
8394                    km += ' ' + fmt % self.__kmatrix__[x, y]
8395
8396        if km_trunc:
8397            km += '\n\n' + '* indicates truncated (col) reaction:\n('
8398            for x in range(len(self.kmatrix_col)):
8399                try:
8400                    ctr.index(x)
8401                    km += self.__reactions__[self.kmatrix_col[x]] + ', '
8402                except:
8403                    pass
8404            km += ')'
8405        if km_trunc2:
8406            km += '\n\n' + '* indicates truncated (row) reaction:\n('
8407            for x in range(len(self.kmatrix_row)):
8408                try:
8409                    rtr.index(x)
8410                    km += self.__reactions__[self.kmatrix_row[x]] + ', '
8411                except:
8412                    pass
8413            km += ')'
8414
8415        if File != None:
8416            print('\nKernel matrix (K)')
8417            File.write('\n\n## Kernel matrix (K)\n')
8418            if not self.__HAS_FLUX_CONSERVATION__:
8419                File.write('No flux conservation\n')
8420            else:
8421                File.write(km)
8422                File.write('\n')
8423        else:
8424            print('\nKernel matrix (K)')
8425            if not self.__HAS_FLUX_CONSERVATION__:
8426                print('No flux conservation')
8427            else:
8428                print(km)
8429
8430    def showL(self, File=None, fmt='%2.3f'):
8431        """
8432        showL(File=None,fmt='%2.3f')
8433
8434        Print the Link matrix (L), including row and column labels to screen or File.
8435
8436        Arguments:
8437
8438        File [default=None]: an open, writable Python file object
8439        fmt [default='%2.3f']: output number format
8440        """
8441        km_trunc = 0
8442        km_trunc2 = 0
8443        km = ''
8444        ctr = []
8445        rtr = []
8446        for a in range(len(self.lmatrix_col)):
8447            if a == 0:
8448                km += 8 * ' '
8449            if len(self.__species__[self.lmatrix_col[a]]) > 7:
8450                km += self.__species__[self.lmatrix_col[a]][:6] + '* '
8451                km_trunc = 1
8452                ctr.append(a)
8453            else:
8454                km += (
8455                    self.__species__[self.lmatrix_col[a]]
8456                    + (9 - len(self.__species__[self.lmatrix_col[a]]) - 1) * ' '
8457                )
8458
8459        for x in range(len(self.lmatrix_row)):
8460            if len(self.__species__[self.lmatrix_row[x]]) > 6:
8461                km += '\n' + self.__species__[self.lmatrix_row[x]][:5] + '* '
8462                km_trunc2 = 1
8463                rtr.append(x)
8464            else:
8465                km += (
8466                    '\n'
8467                    + self.__species__[self.lmatrix_row[x]]
8468                    + (8 - len(self.__species__[self.lmatrix_row[x]]) - 1) * ' '
8469                )
8470            for y in range(len(self.lmatrix_col)):
8471                if y != 0:
8472                    km += 4 * ' '
8473                if abs(self.__lmatrix__[x, y]) == 0.0:
8474                    km += ' ' + fmt % abs(self.__lmatrix__[x, y])
8475                elif self.__lmatrix__[x, y] < 0.0:
8476                    km += fmt % self.__lmatrix__[x, y]
8477                else:
8478                    km += ' ' + fmt % self.__lmatrix__[x, y]
8479
8480        if km_trunc:
8481            km += '\n\n' + '* indicates truncated (col) species:\n('
8482            for x in range(len(self.lmatrix_col)):
8483                try:
8484                    ctr.index(x)
8485                    km += self.__species__[self.lmatrix_col[x]] + ', '
8486                except:
8487                    pass
8488            km += ')'
8489        if km_trunc2:
8490            km += '\n\n' + '* indicates truncated (row) species:\n('
8491            for x in range(len(self.lmatrix_row)):
8492                try:
8493                    rtr.index(x)
8494                    km += self.__species__[self.lmatrix_row[x]] + ', '
8495                except:
8496                    pass
8497            km += ')'
8498
8499        if File != None:
8500            print('\nLink matrix (L)')
8501            File.write('\n\n## Link matrix (L)\n')
8502            if not self.__HAS_MOIETY_CONSERVATION__:
8503                File.write('No moiety conservation\n')
8504                File.write(km)
8505                File.write('\n')
8506            else:
8507                File.write(km)
8508                File.write('\n')
8509        else:
8510            print('\nLink matrix (L)')
8511            if not self.__HAS_MOIETY_CONSERVATION__:
8512                print('\"No moiety conservation\"\n')
8513                print(km)
8514            else:
8515                print(km)
8516
8517    # Internal test functions
8518    def TestSimState(self, endTime=10000, points=101, diff=1.0e-5):
8519        """
8520        **Deprecated**
8521        """
8522        pass
8523
8524    def ResetNumberFormat(self):
8525        """
8526        ResetNumberFormat()
8527
8528        Reset PySCeS default number format stored as mod.mode_number format to %2.4e
8529
8530        Arguments:
8531        None
8532
8533        """
8534        self.__settings__['mode_number_format'] = '%2.4e'
8535
8536    def Write_array(
8537        self, inputd, File=None, Row=None, Col=None, close_file=0, separator='  '
8538    ):
8539        """
8540        Write_array(input,File=None,Row=None,Col=None,close_file=0,separator='  ')
8541
8542        Write an array to File with optional row/col labels. A ',' separator can be specified to create
8543        a CSV style file.
8544
8545        mod.__settings__['write_array_header']: add <filename> as a header line (1 = yes, 0 = no)
8546        mod.__settings__['write_array_spacer']: add a space after the header line (1 = yes, 0 = no)
8547        mod.__settings__['write_arr_lflush']: set the flush rate for large file writes
8548
8549        Arguments:
8550
8551        input: the array to be written
8552        File [default=None]: an open, writable Python file object
8553        Row [default=None]: a list of row labels
8554        Col [default=None]: a list of column labels
8555        close_file [default=0]: close the file after write (1) or leave open (0)
8556        separator [default='  ']: the column separator to use
8557
8558        """
8559        fname = (
8560            'Write_array_'
8561            + self.ModelFile.replace('.psc', '')
8562            + '_'
8563            + time.strftime("%H:%M:%S")
8564        )
8565
8566        # seeing as row indexes are now tuples and not arrays - brett 20050713
8567        if type(inputd) == tuple or type(inputd) == list:
8568            inputd = numpy.array(inputd)
8569
8570        if Row != None:
8571            assert (
8572                len(Row) == inputd.shape[0]
8573            ), 'len(Row) must be equal to len(input_row)'
8574        if Col != None:
8575            assert (
8576                len(Col) == inputd.shape[1]
8577            ), 'len(Col) must be equal to len(input_col)'
8578
8579        if File != None:
8580            # assert File != file, 'WriteArray(input,File=None,Row=None,Col=None,close_file=0)'
8581            if self.__settings__['write_array_header']:
8582                File.write('\n## ' + fname + '\n')
8583            if self.__settings__['write_array_spacer']:
8584                File.write('\n')
8585            if Col != None:
8586                print('Writing column')
8587                File.write('#')
8588                try:
8589                    input_width = (
8590                        len(self.__settings__['mode_number_format'] % inputd[0, 0])
8591                        + 1
8592                        + len(str(separator))
8593                    )
8594                except:
8595                    input_width = (
8596                        len(self.__settings__['mode_number_format'] % inputd[0])
8597                        + 1
8598                        + len(str(separator))
8599                    )
8600                for x in Col:
8601                    if len(str(x)) <= len(str(inputd[0, 0])):
8602                        spacer = (input_width - len(str(x))) * ' '
8603                        File.write(str(x) + spacer)
8604                    else:
8605                        spacer = (len(str(separator))) * ' '
8606                        File.write(str(x[:input_width]) + spacer)
8607                File.write('\n')
8608            try:
8609                print('\nWriting array (normal) to file')
8610                # scipy.io.write_array(File,input,separator=' ',linesep='\n',precision=3,keep_open=1)
8611                flush_count = 0
8612                if self.__settings__['write_arr_lflush'] < 2:
8613                    print('INFO: LineFlush must be >= 2')
8614                    self.__settings__['write_arr_lflush'] = 2
8615                for x in range(inputd.shape[0]):
8616                    flush_count += 1
8617                    for y in range(inputd.shape[1]):
8618                        if inputd[x, y] < 0.0:
8619                            File.write(
8620                                self.__settings__['mode_number_format'] % inputd[x, y]
8621                            )
8622                        else:
8623                            File.write(
8624                                ' '
8625                                + self.__settings__['mode_number_format'] % inputd[x, y]
8626                            )
8627                        if y < inputd.shape[1] - 1:
8628                            File.write(separator)
8629                    File.write('\n')
8630                    if flush_count == self.__settings__['write_arr_lflush']:
8631                        File.flush()
8632                        flush_count = 0
8633                        # print 'flushing'
8634                # File.write('\n')
8635            except IndexError as e:
8636                print('\nWriting vector (normal) to file')
8637                for x in range(len(inputd)):
8638                    File.write(self.__settings__['mode_number_format'] % inputd[x])
8639                    if x < len(inputd) - 1:
8640                        File.write(separator)
8641                File.write('\n')
8642            if Row != None:
8643                print('Writing row')
8644                File.write('# Row: ')
8645                for x in Row:
8646                    File.write(str(x) + separator)
8647                File.write('\n')
8648            if close_file:
8649                File.close()
8650        else:
8651            print(
8652                'INFO: You need to supply an open writable file as the 2nd argument to this function'
8653            )
8654
8655    def Write_array_latex(self, inputa, File=None, Row=None, Col=None, close_file=0):
8656        """
8657        Write_array_latex(input,File=None,Row=None,Col=None,close_file=0)
8658
8659        Write an array to an open file as a \'LaTeX\' {array}
8660
8661        Arguments:
8662
8663        input: the array to be written
8664        File [default=None]: an open, writable Python file object
8665        Row [default=None]: a list of row labels
8666        Col [default=None]: a list of column labels
8667        close_file [default=0]: close the file after write (1) or leave open (0)
8668
8669        """
8670        # seeing as row indexes are now tuples and not arrays - brett 20050713
8671        if type(inputa) == tuple or type(inputa) == list:
8672            inputa = numpy.array(inputa)
8673
8674        try:
8675            a = inputa.shape[1]
8676        except:
8677            inputa.shape = (1, inputa.shape[0])
8678
8679        if Row != None:
8680            assert (
8681                len(Row) == inputa.shape[0]
8682            ), 'len(Row) must be equal to len(input_row)'
8683            print('Writing row')
8684        if Col != None:
8685            assert (
8686                len(Col) == inputa.shape[1]
8687            ), 'len(Col) must be equal to len(input_col)'
8688            print('Writing column')
8689
8690        if self.__settings__['write_arr_lflush'] < 2:
8691            print('INFO: LineFlush must be >= 2')
8692            self.__settings__['write_arr_lflush'] = 2
8693
8694        fname = (
8695            'Write_array_latex_'
8696            + self.ModelFile.replace('.psc', '')
8697            + '_'
8698            + time.strftime("%H:%M:%S")
8699        )
8700        if File != None:
8701            # assert File != file, 'WriteArray(input,File=None,Row=None,Col=None,close_file=0)'
8702            File.write('\n%% ' + fname + '\n')
8703            try:
8704                a = inputa.shape
8705                print('\nWriting array (LaTeX) to file')
8706                File.write('\\[\n')
8707                File.write('\\begin{array}{')
8708                if Row != None:
8709                    File.write('r|')
8710                for x in range(inputa.shape[1]):
8711                    File.write('r')
8712
8713                File.write('}\n ')
8714                flush_count = 0
8715                for x in range(inputa.shape[0]):
8716                    if Col != None and x == 0:
8717                        for el in range(len(Col)):
8718                            elx = str(Col[el]).replace('_', '\_')
8719                            if Row != None:
8720                                File.write(' & $\\small{' + elx + '}$')
8721                            else:
8722                                if el == 0:
8723                                    File.write(' $\\small{' + elx + '}$')
8724                                else:
8725                                    File.write(' & $\\small{' + elx + '}$')
8726
8727                        File.write(' \\\\ \\hline\n ')
8728                    flush_count += 1
8729                    if Row != None:
8730                        el2 = str(Row[x]).replace('_', '\\_')
8731                        File.write('$\\small{' + el2 + '}$ &')
8732
8733                    for y in range(inputa.shape[1]):
8734                        if inputa[x, y] < 0.0:
8735                            val = '%2.4f' % inputa[x, y]
8736                        else:
8737                            val = ' ' + '%2.4f' % inputa[x, y]
8738
8739                        File.write(val)
8740                        if y < inputa.shape[1] - 1:
8741                            File.write(' &')
8742                    File.write(' \\\\\n ')
8743                    if flush_count == self.__settings__['write_arr_lflush']:
8744                        File.flush()
8745                        flush_count = 0
8746                        # print 'flushing'
8747                # File.write('\n')
8748                File.write('\\end{array}\n')
8749                File.write('\\]\n\n')
8750            except:
8751                print(
8752                    '\nINFO: Only arrays can currently be processed with this method.\n'
8753                )
8754            if close_file:
8755                File.close()
8756        else:
8757            print(
8758                'INFO: You need to supply an open writable file as the 2nd argument to this method'
8759            )
8760
8761    def Write_array_html(
8762        self, inputa, File=None, Row=None, Col=None, name=None, close_file=0
8763    ):
8764        """
8765        Write_array_html(input,File=None,Row=None,Col=None,name=None,close_file=0)
8766
8767        Write an array as an HTML table (no header/footer) or complete document. Tables
8768        are formatted with coloured columns if they exceed a specified size.
8769
8770        mod.__settings__['write_array_html_header']: write the HTML document header
8771        mod.__settings__['write_array_html_footer']: write the HTML document footer
8772
8773        Arguments:
8774
8775        input: the array to be written
8776        File [default=None]: an open, writable Python file object
8777        Row [default=None]: a list of row labels
8778        Col [default=None]: a list of column labels
8779        name [default=None]: an HTML table description line
8780        close_file [default=0]: close the file after write (1) or leave open (0)
8781
8782        """
8783        # seeing as row indexes are now tuples and not arrays - brett 20050713
8784        if type(inputa) == tuple or type(inputa) == list:
8785            inputa = numpy.array(inputa)
8786        try:
8787            a = inputa.shape[1]
8788        except:
8789            inputa.shape = (1, inputa.shape[0])
8790
8791        if Row != None:
8792            assert (
8793                len(Row) == inputa.shape[0]
8794            ), 'len(Row) must be equal to len(input_row)'
8795            print('Writing row')
8796        if Col != None:
8797            assert (
8798                len(Col) == inputa.shape[1]
8799            ), 'len(Col) must be equal to len(input_col)'
8800            print('Writing column')
8801        if self.__settings__['write_arr_lflush'] < 2:
8802            print('INFO: LineFlush must be >= 2')
8803            self.__settings__['write_arr_lflush'] = 2
8804        if name != None:
8805            fname = (
8806                'PySCeS data "'
8807                + name
8808                + '" generated from model file: '
8809                + self.ModelFile
8810                + ' '
8811                + time.strftime("%H:%M:%S")
8812            )
8813        else:
8814            fname = (
8815                'PySCeS data generated from model file: '
8816                + self.ModelFile
8817                + ' '
8818                + time.strftime("%H:%M:%S")
8819            )
8820        if File != None:
8821            # assert File != file, 'WriteArray(input,File=None,Row=None,Col=None,close_file=0)'
8822            header = '\n'
8823            header += (
8824                '<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">\n'
8825            )
8826            header += '<html>\n'
8827            header += '<head>\n'
8828            header += (
8829                '<title>PySCeS data generated from: '
8830                + self.ModelFile
8831                + ' - '
8832                + time.strftime("%H:%M:%S (%Z)")
8833                + '</title>\n'
8834            )
8835            header += '<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">\n'
8836            header += '</head>\n'
8837            header += '<body>\n\n'
8838            header += (
8839                '<h4><a href="http://pysces.sourceforge.net">PySCeS</a> data generated from: '
8840                + self.ModelFile
8841                + '</h4>\n\n'
8842            )
8843
8844            if self.__settings__['write_array_html_header']:
8845                File.write(header)
8846            File.write('<!-- ' + fname + '-->\n')
8847            if name != None:
8848                File.write(
8849                    '\n<p>\n<font face="Arial, Helvetica, sans-serif"><strong>'
8850                    + name
8851                    + '</strong></font>'
8852                )
8853            else:
8854                File.write('\n<p>\n')
8855            File.write(
8856                '\n<table border="1" cellpadding="2" cellspacing="2" bgcolor="#FFFFFF">'
8857            )
8858            try:
8859                a = inputa.shape
8860                print('Writing array (HTML) to file')
8861
8862                double_index = 15
8863
8864                if Col != None:
8865                    File.write('\n<tr>\n')
8866                    if Row != None:
8867                        File.write('  <td>&nbsp;</td>\n')
8868                    for col in Col:
8869                        File.write(
8870                            '  <td bgcolor="#CCCCCC"><div align="center"><b>'
8871                            + str(col)
8872                            + '</b></div></td>\n'
8873                        )
8874                    if Row != None and inputa.shape[1] + 1 >= double_index:
8875                        File.write('  <td>&nbsp;</td>\n')
8876                    File.write('</tr>')
8877
8878                flush_count = 0
8879                colour_count_row = 6
8880                colour_count_col = 6
8881                rowcntr = 0
8882                html_colour = '#FFFFCC'
8883                for x in range(inputa.shape[0]):
8884                    rowcntr += 1
8885                    if (
8886                        rowcntr == colour_count_row
8887                        and inputa.shape[0] > 3 * colour_count_row
8888                    ):
8889                        File.write('\n<tr bgcolor="' + html_colour + '">\n')
8890                        rowcntr = 0
8891                    else:
8892                        File.write('\n<tr>\n')
8893                    if Row != None:
8894                        File.write(
8895                            '  <td bgcolor="#CCCCCC"><div align="center"><b>'
8896                            + str(Row[x])
8897                            + '</b></div></td>\n'
8898                        )
8899                    colcntr = 0
8900                    for y in range(inputa.shape[1]):
8901                        colcntr += 1
8902                        if (
8903                            colcntr == colour_count_col
8904                            and inputa.shape[1] > 3 * colour_count_row
8905                        ):
8906                            File.write(
8907                                '  <td nowrap bgcolor="'
8908                                + html_colour
8909                                + '">'
8910                                + self.__settings__['write_array_html_format']
8911                                % inputa[x, y]
8912                                + '</td>\n'
8913                            )
8914                            colcntr = 0
8915                        else:
8916                            File.write(
8917                                '  <td nowrap>'
8918                                + self.__settings__['write_array_html_format']
8919                                % inputa[x, y]
8920                                + '</td>\n'
8921                            )
8922                    ##                        File.write('  <td nowrap>' + self.__settings__['write_array_html_format'] % input[x,y] + '</td>\n')
8923                    if Row != None and inputa.shape[1] + 1 >= double_index:
8924                        File.write(
8925                            '  <td bgcolor="#CCCCCC"><div align="center"><b>'
8926                            + Row[x]
8927                            + '</b></div></td>\n'
8928                        )
8929                    File.write('</tr>')
8930
8931                    if flush_count == self.__settings__['write_arr_lflush']:
8932                        File.flush()
8933                        flush_count = 0
8934                        # print 'flushing'
8935                if Col != None and inputa.shape[0] + 1 >= double_index:
8936                    File.write('\n<tr>\n')
8937                    if Row != None:
8938                        File.write('  <td>&nbsp;</td>\n')
8939                    for col in Col:
8940                        File.write(
8941                            '  <td bgcolor="#CCCCCC"><div align="center"><b>'
8942                            + col
8943                            + '</b></div></td>\n'
8944                        )
8945                    if Row != None and inputa.shape[1] + 1 >= double_index:
8946                        File.write('  <td>&nbsp;</td>\n')
8947                    File.write('</tr>\n')
8948            except:
8949                print(
8950                    '\nINFO: Only arrays can currently be processed with this method.\n'
8951                )
8952
8953            File.write('\n</table>\n')
8954            File.write('</p>\n\n')
8955            if self.__settings__['write_array_html_footer']:
8956                try:
8957                    File.write(
8958                        '<p><a href="http://pysces.sourceforge.net"><font size="3">PySCeS '
8959                        + __version__
8960                        + '</font></a><font size="2"> HTML output (model <i>'
8961                        + self.ModelFile
8962                        + '</i> analysed at '
8963                        + time.strftime("%H:%M:%S")
8964                        + ' by <i>'
8965                        + getuser()
8966                        + '</i>)</font></p>\n'
8967                    )
8968                except:
8969                    File.write(
8970                        '<p><a href="http://pysces.sourceforge.net"><font size="3">PySCeS '
8971                        + __version__
8972                        + '</font></a><font size="2"> HTML output (model <i>'
8973                        + self.ModelFile
8974                        + '</i> analysed at '
8975                        + time.strftime("%H:%M:%S - %Z")
8976                        + ')</font></p>\n'
8977                    )
8978                File.write('</body>\n')
8979                File.write('</html>\n')
8980            else:
8981                File.write('\n')
8982
8983            if close_file:
8984                File.close()
8985        else:
8986            print(
8987                'INFO: You need to supply an open writable file as the 2nd argument to this method'
8988            )
8989
8990    def SimPlot(
8991        self, plot='species', filename=None, title=None, log=None, format='lines'
8992    ):
8993        """
8994        Plot the simulation results, uses the new UPI pysces.plt interface:
8995
8996        - *plot*: output to plot (default='species')
8997
8998         - 'all' rates and species
8999         - 'species' species
9000         - 'rates' reaction rates
9001         - `['S1', 'R1', ]` a list of model attributes (species, rates)
9002
9003        - *filename* if not None file is exported to filename (default=None)
9004        - *title* the plot title (default=None)
9005        - *log* use log axis for 'x', 'y', 'xy' (default=None)
9006        - *format* line format, backend dependant (default='')
9007        """
9008        data = None
9009        labels = None
9010        allowedplots = ['all', 'species', 'rates']
9011        if type(plot) != list and plot not in allowedplots:
9012            raise RuntimeError(
9013                '\nPlot must be one of {} not \"{}\"'.format(str(allowedplots), plot)
9014            )
9015        if plot == 'all':
9016            data, labels = self.data_sim.getAllSimData(lbls=True)
9017        elif plot == 'species':
9018            data, labels = self.data_sim.getSpecies(lbls=True)
9019        elif plot == 'rates':
9020            data, labels = self.data_sim.getRates(lbls=True)
9021        else:
9022            plot = [
9023                at
9024                for at in plot
9025                if at
9026                in self.__species__ + self.__reactions__ + [self.data_sim.time_label]
9027            ]
9028            kwargs = {'lbls': True}
9029            if len(plot) > 0:
9030                data, labels = self.data_sim.getSimData(*plot, **kwargs)
9031        del allowedplots
9032        self.__SIMPLOT_OUT__ = labels
9033        plt.plotLines(
9034            data, 0, list(range(1, data.shape[1])), titles=labels, formats=[format]
9035        )
9036        # set the x-axis range so that it is original range + 0.2*sim_end
9037        # this is a sceintifcally dtermned amount of space that is needed for the title at the
9038        # end of the line :-) - brett 20040209
9039        RngTime = self.data_sim.getTime()
9040        end = RngTime[-1] + 0.2 * RngTime[-1]
9041        plt.setRange('x', RngTime[0], end)
9042        del RngTime
9043
9044        if self.__KeyWords__['Output_In_Conc']:
9045            M = 'Concentration'
9046        else:
9047            M = (
9048                'Amount (%(multiplier)s x %(kind)s x 10**%(scale)s)**%(exponent)s'
9049                % self.__uDict__['substance']
9050            )
9051        xu = (
9052            'Time (%(multiplier)s x %(kind)s x 10**%(scale)s)**%(exponent)s'
9053            % self.__uDict__['time']
9054        )
9055        plt.setAxisLabel('x', xu)
9056
9057        if plot == 'all':
9058            yl = 'Rates, {}'.format(M)
9059        elif plot == 'rates':
9060            yl = 'Rate'
9061        elif plot == 'species':
9062            yl = '{}'.format(M)
9063        else:
9064            yl = 'Rates, {}'.format(M)
9065        plt.setAxisLabel('y', yl)
9066        if log != None:
9067            plt.setLogScale(log)
9068        if title == None:
9069            plt.setGraphTitle(
9070                'PySCeS Simulation ('
9071                + self.ModelFile
9072                + ') '
9073                + time.strftime("%a, %d %b %Y %H:%M:%S")
9074            )
9075        else:
9076            plt.setGraphTitle(title)
9077        plt.replot()
9078        if filename != None:
9079            plt.export(filename, directory=self.ModelOutput, type='png')
9080
9081    def Scan1(self, range1=[], runUF=0):
9082        """
9083        Scan1(range1=[],runUF=0)
9084
9085        Perform a single dimension parameter scan using the steady-state solvers. The parameter to be scanned is defined (as a model attribute "P") in mod.scan_in while the required output is entered into the list mod.scan_out.
9086        Results of a parameter scan can be easilly viewed with Scan1Plot().
9087
9088        mod.scan_in - a model attribute written as in the input file (eg. P, Vmax1 etc)
9089        mod.scan_out -  a list of required output ['A','T2', 'ecR1_s1', 'ccJR1_R1', 'rcJR1_s1', ...]
9090        mod.scan_res -  the results of a parameter scan
9091        mod.scan - numpy record array with the scan results (scan_in and scan_out), call as mod.scan.Vmax, mod.scan.A_ss, mod.scan.J_R1, etc.
9092        mod.__settings__["scan1_mca_mode"] - force the scan algorithm to evaluate the elasticities (1) and control coefficients (2)
9093        (this should also be auto-detected by the Scan1 method).
9094
9095        Arguments:
9096
9097        range1 [default=[]]: a predefined range over which to scan.
9098        runUF [default=0]: run (1) the user defined function mod.User_Function (!U) before evaluating the steady state.
9099
9100        """
9101        if self.__settings__["scan1_dropbad"] != 0:
9102            print(
9103                '\n****\nINFO: Dropping invalid steady states can mask interesting behaviour\n****\n'
9104            )
9105
9106        # check the legitimacy of the input and output lists
9107        # self.__settings__["scan1_mca_mode"] = scanMCA
9108        run = 1
9109        # we now accept parameters and intial species values as scanable values - brett 20060519
9110        parameters = list(self.__parameters__) + [a + '_init' for a in self.__species__]
9111        scanpar = []
9112
9113        scanIn = [self.scan_in]
9114
9115        for x in scanIn:
9116            try:
9117                a = parameters.index(x)
9118                scanpar.append(x)
9119            except:
9120                print(x, ' is not a parameter')
9121        if len(scanpar) < 1:
9122            print('mod.scan_in should contain something')
9123            run = 0
9124        elif len(scanpar) > 1:
9125            print('Only 1D scans possible - first element will be used')
9126            scanpar = scanpar[0]
9127            print('self.scan_in = ', scanpar)
9128        else:
9129            scanpar = scanpar[0]
9130
9131        self.scan_in = scanpar
9132
9133        wrong = []
9134
9135        for x in self.scan_out:
9136            if x[:2] == 'ec' and self.__settings__["scan1_mca_mode"] < 2:
9137                self.__settings__["scan1_mca_mode"] = 1
9138            elif x[:2] == 'cc':
9139                self.__settings__["scan1_mca_mode"] = 2
9140            elif x[:2] == 'rc':
9141                self.__settings__["scan1_mca_mode"] = 3
9142
9143        if self.__settings__["scan1_mca_mode"] == 1:
9144            self.doElas()
9145        elif self.__settings__["scan1_mca_mode"] == 2:
9146            self.doMca()
9147        elif self.__settings__["scan1_mca_mode"] == 3:
9148            self.doMcaRC()
9149        else:
9150            self.doState()  # we are going to run a whole bunch anyway
9151
9152        for x in self.scan_out:
9153            try:
9154                if x.startswith('ec'):
9155                    try:
9156                        getattr(self.ec, x[2:])
9157                    except AttributeError:
9158                        getattr(self.ecp, x[2:])
9159                elif x.startswith('cc'):
9160                    if x.startswith('ccJ'):
9161                        getattr(self.cc, x[3:])
9162                    else:
9163                        getattr(self.cc, x[2:])
9164                elif x.startswith('rc'):
9165                    if x.startswith('rcJ'):
9166                        getattr(self.rc, x[3:])
9167                    else:
9168                        getattr(self.rc, x[2:])
9169                elif x in self.reactions:
9170                    getattr(self.data_sstate, x)
9171                    print(
9172                        "INFO: using steady-state flux for reaction ({} --> J_%s)".format(
9173                            x, x
9174                        )
9175                    )
9176                    self.scan_out[self.scan_out.index(x)] = 'J_{}'.format(x)
9177                elif x.startswith('J_'):
9178                    getattr(self.data_sstate, x[2:])
9179                elif x in self.species:
9180                    getattr(self.data_sstate, x)
9181                    print(
9182                        "INFO: using steady-state concentration for species ({} --> {}_ss)".format(
9183                            x, x
9184                        )
9185                    )
9186                    self.scan_out[self.scan_out.index(x)] = '{}_ss'.format(x)
9187                elif x.endswith('_ss'):
9188                    getattr(self.data_sstate, x[:-3])
9189                else:
9190                    getattr(self, x)
9191            except:
9192                print(x + ' is not a valid attribute')
9193                wrong.append(x)
9194            if x == self.scan_in:
9195                wrong.append(x)
9196                print(x, ' is mod.scan_in ')
9197
9198        if len(wrong) != 0:
9199            try:
9200                for x in wrong:
9201                    print(self.scan_out)
9202                    self.scan_out.remove(x)
9203                    print(self.scan_out)
9204            except:
9205                print('No valid output')
9206                run = 0
9207
9208        assert (
9209            len(self.scan_out) != 0
9210        ), "Output parameter list (mod.scan_out) empty - do the model attributes exist ... see manual for details."
9211
9212        # print run, self.scan_in, self.scan_out
9213
9214        self._scan = None
9215        result = []
9216        cntr = 0
9217        cntr2 = 1
9218        cntr3 = 0
9219
9220        # reset scan error controls
9221        self.__scan_errors_par__ = None
9222        self.__scan_errors_idx__ = None
9223
9224        if self.__settings__['scan1_mesg']:
9225            print('\nScanning ...')
9226        if len(self.scan_in) > 0 and run == 1:
9227            badList = []
9228            badList_idx = []
9229            if self.__settings__['scan1_mesg']:
9230                print(len(range1) - (cntr * cntr2), end=' ')
9231            for xi in range(len(range1)):
9232                x = range1[xi]
9233                setattr(self, self.scan_in, x)
9234                ##  exec('self.' + self.scan_in + ' = ' + `x`)
9235                if self.__settings__["scan1_mca_mode"] == 1:
9236                    self.doElas()
9237                elif self.__settings__["scan1_mca_mode"] == 2:
9238                    self.doMca()
9239                elif self.__settings__["scan1_mca_mode"] == 3:
9240                    self.doMcaRC()
9241                else:
9242                    self.State()
9243                rawres = [x]
9244                # these two lines are going to be terminated - brett
9245                # rawres.append(x)
9246                # rawres.insert(0,x)
9247                if runUF:
9248                    self.User_Function()
9249                for res in self.scan_out:
9250                    if res.startswith('ec'):
9251                        try:
9252                            rawres.append(getattr(self.ec, res[2:]))
9253                        except AttributeError:
9254                            rawres.append(getattr(self.ecp, res[2:]))
9255                    elif res.startswith('cc'):
9256                        if res.startswith('ccJ'):
9257                            rawres.append(getattr(self.cc, res[3:]))
9258                        else:
9259                            rawres.append(getattr(self.cc, res[2:]))
9260                    elif res.startswith('rc'):
9261                        if res.startswith('rcJ'):
9262                            rawres.append(getattr(self.rc, res[3:]))
9263                        else:
9264                            rawres.append(getattr(self.rc, res[2:]))
9265                    elif res in self.reactions:
9266                        rawres.append(getattr(self.data_sstate, res))
9267                    elif res.startswith('J_'):
9268                        rawres.append(getattr(self.data_sstate, res[2:]))
9269                    elif res in self.species:
9270                        rawres.append(getattr(self.data_sstate, res))
9271                    elif res.endswith('_ss'):
9272                        rawres.append(getattr(self.data_sstate, res[:-3]))
9273                    else:
9274                        rawres.append(getattr(self, res))
9275
9276                # The following is for user friendly reporting:
9277                # next we check if the state is ok : if bad report it and add it : if good add it
9278                if not self.__StateOK__ and self.__settings__["scan1_dropbad"] != 0:
9279                    pass
9280                elif not self.__StateOK__:
9281                    if self.__settings__["scan1_nan_on_bad"]:
9282                        result.append([numpy.NaN] * len(rawres))
9283                    else:
9284                        result.append(rawres)
9285                    badList.append(x)
9286                    badList_idx.append(xi)
9287                else:
9288                    result.append(rawres)
9289                cntr += 1
9290                cntr3 += 1
9291                if cntr == 20:
9292                    if self.__settings__['scan1_mesg']:
9293                        print(len(range1) - (cntr * cntr2), end=' ')
9294                    cntr = 0
9295                    cntr2 += 1
9296                if cntr3 == 101:
9297                    if self.__settings__['scan1_mesg']:
9298                        print(' ')
9299                    cntr3 = 0
9300        if self.__settings__['scan1_mesg']:
9301            print('\ndone.\n')
9302        if len(badList) != 0:
9303            self.__scan_errors_par__ = badList
9304            self.__scan_errors_idx__ = badList_idx
9305            print(
9306                '\nINFO: '
9307                + str(len(badList))
9308                + ' invalid steady states detected at '
9309                + self.scan_in
9310                + ' values:'
9311            )
9312            print(badList)
9313        if len(result) == 0:
9314            self.scan_res = numpy.zeros((len(range1), len(self.scan_out) + 1))
9315        else:
9316            self.scan_res = numpy.array(result)
9317
9318    @property
9319    def scan(self):
9320        if self._scan is None and self.scan_res is not None:
9321            self._scan = numpy.rec.fromrecords(
9322                self.scan_res, names=[self.scan_in] + self.scan_out
9323            )
9324        return self._scan
9325
9326    def Scan1Plot(self, plot=[], title=None, log=None, format='lines', filename=None):
9327        """
9328        Plot the results of a parameter scan generated with **Scan1()**
9329
9330        - *plot* if empty mod.scan_out is used, otherwise any subset of mod.scan_out (default=[])
9331        - *filename* the filename of the PNG file (default=None, no export)
9332        - *title* the plot title (default=None)
9333        - *log* if None a linear axis is assumed otherwise one of ['x','xy','xyz'] (default=None)
9334        - *format* the backend dependent line format (default='lines')  or the *CommonStyle* 'lines' or 'points'.
9335        """
9336        data = self.scan_res
9337        labels = None
9338        if type(plot) == str:
9339            plot = [plot]
9340        if len(plot) == 0:
9341            plot = self.scan_out
9342        else:
9343            plot = [at for at in plot if hasattr(self, at)]
9344        if len(plot) == 0:
9345            print('No plottable output specified using self.scan_out')
9346            plot = self.scan_out
9347
9348        plotidx = [self.scan_out.index(c) + 1 for c in plot]
9349        labels = [self.scan_in] + self.scan_out
9350        plt.plotLines(data, 0, plotidx, titles=labels, formats=[format])
9351
9352        end = data[-1, 0] + 0.2 * data[-1, 0]
9353        plt.setRange('x', data[0, 0], end)
9354        plt.setAxisLabel('x', self.scan_in)
9355
9356        yl = 'Steady-state variable'
9357        plt.setAxisLabel('y', yl)
9358        if log != None:
9359            plt.setLogScale(log)
9360        if title == None:
9361            plt.setGraphTitle(
9362                'PySCeS Scan1 ('
9363                + self.ModelFile
9364                + ') '
9365                + time.strftime("%a, %d %b %Y %H:%M:%S")
9366            )
9367        else:
9368            plt.setGraphTitle(title)
9369        plt.replot()
9370        if filename != None:
9371            plt.export(filename, directory=self.ModelOutput, type='png')
9372
9373    def Scan2D(self, p1, p2, output, log=False):
9374        """
9375        Generate a 2 dimensional parameter scan using the steady-state solvers.
9376
9377        - *p1* is a list of [parameter1, start, end, points]
9378        - *p2* is a list of [parameter2, start, end, points]
9379        - *output* steady-state variable/properties e.g. 'J_R1', 'A_ss', 'ecR1_s1'
9380        - *log* scan using log ranges for both axes
9381        """
9382
9383        for p in [p1, p2]:
9384            if not hasattr(self, p[0]):
9385                raise RuntimeError('\"{}\" is not a valid model attribute'.format(p[0]))
9386
9387        p1 = list(p1) + [log, False]
9388        p2 = list(p2) + [log, False]
9389
9390        sc1 = Scanner(self)
9391        sc1.quietRun = True
9392        sc1.addScanParameter(*p1)
9393        sc1.addScanParameter(*p2)
9394        sc1.addUserOutput(output)
9395        sc1.Run()
9396
9397        self.__scan2d_pars__ = [p1[0], p2[0], output]
9398        self.__scan2d_results__ = sc1.getResultMatrix()
9399        del sc1
9400
9401    def Scan2DPlot(self, title=None, log=None, format='lines', filename=None):
9402        """
9403        Plot the results of a 2D scan generated with Scan2D
9404
9405        - *filename* the filename of the PNG file (default=None, no export)
9406        - *title* the plot title (default=None)
9407        - *log* if None a linear axis is assumed otherwise one of ['x','xy','xyz'] (default=None)
9408        - *format* the backend dependent line format (default='lines')  or the *CommonStyle* 'lines' or 'points'.
9409        """
9410
9411        plt.splot(
9412            self.__scan2d_results__, 0, 1, 2, titles=self.__scan2d_pars__, format=format
9413        )
9414        plt.setRange('xyz')
9415        plt.setAxisLabel('x', self.__scan2d_pars__[0])
9416        plt.setAxisLabel('y', self.__scan2d_pars__[1])
9417        plt.setAxisLabel('z', 'Steady-state variable')
9418        if log != None:
9419            plt.setLogScale(log)
9420        if title == None:
9421            plt.setGraphTitle(
9422                'PySCeS Scan2D ('
9423                + self.ModelFile
9424                + ') '
9425                + time.strftime("4a, %d %b %Y %H:%M:%S")
9426            )
9427        else:
9428            plt.setGraphTitle(title)
9429        plt.replot()
9430        if filename != None:
9431            plt.export(filename, directory=self.ModelOutput, type='png')
9432
9433    def SetQuiet(self):
9434        """
9435        SetQuiet()
9436
9437        Turn off as much solver reporting noise as possible:
9438        mod.__settings__['hybrd_mesg'] = 0
9439        mod.__settings__['nleq2_mesg'] = 0
9440        mod.__settings__["lsoda_mesg"] = 0
9441        mod.__settings__['mode_state_mesg'] = 0
9442        mod.__settings__['scan1_mesg'] = 0
9443        mod.__settings__['solver_switch_warning'] = False
9444
9445        Arguments:
9446        None
9447
9448        """
9449        self.__settings__['hybrd_mesg'] = 0
9450        self.__settings__['nleq2_mesg'] = 0
9451        self.__settings__["lsoda_mesg"] = 0
9452        self.__settings__['mode_state_mesg'] = 0
9453        self.__settings__['scan1_mesg'] = 0
9454        self.__settings__['solver_switch_warning'] = False
9455
9456    def SetLoud(self):
9457        """
9458        SetLoud()
9459
9460        Turn on as much solver reporting noise as possible:
9461        mod.__settings__['hybrd_mesg'] = 1
9462        mod.__settings__['nleq2_mesg'] = 1
9463        mod.__settings__["lsoda_mesg"] = 1
9464        mod.__settings__['mode_state_mesg'] = 1
9465        mod.__settings__['scan1_mesg'] = 1
9466        mod.__settings__['solver_switch_warning'] = True
9467
9468        Arguments:
9469        None
9470
9471        """
9472        self.__settings__['hybrd_mesg'] = 1
9473        self.__settings__['nleq2_mesg'] = 1
9474        self.__settings__["lsoda_mesg"] = 1
9475        self.__settings__['mode_state_mesg'] = 1
9476        self.__settings__['scan1_mesg'] = 1
9477        self.__settings__['solver_switch_warning'] = True
9478
9479    def clone(self):
9480        """
9481        Returns a deep copy of this model object (experimental!)
9482
9483        """
9484
9485        print('\nINFO: Cloning function is currently experimental ... use with care.')
9486        return copy.deepcopy(self)
9487
9488
9489class WasteManagement(object):
9490    _gc_delay_hack = (
9491        gplt  # dont f$%^&*)ing even ask ... only 5 hrs to hack this workaround
9492    )
9493
9494
9495__waste_manager = WasteManagement()
9496
9497# if __psyco_active__:
9498# psyco.bind(PysMod)
9499
9500if __name__ == '__main__':
9501    print('\nTo use PySCeS import it from a Python Shell: \n\timport pysces\n')
9502