source: PyFMI/trunk/src/common/io.py

Last change on this file was 13947, checked in by randersson, 5 weeks ago

#5819 Added a method to deal with list of names being of type bytes while accessing them directly from the class attribute name

File size: 76.2 KB
Line 
1#!/usr/bin/env python
2# -*- coding: utf-8 -*-
3
4# Copyright (C) 2010 Modelon AB
5#
6# This program is free software: you can redistribute it and/or modify
7# it under the terms of the GNU Lesser General Public License as published by
8# the Free Software Foundation, version 3 of the License.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13# GNU Lesser General Public License for more details.
14#
15# You should have received a copy of the GNU Lesser General Public License
16# along with this program. If not, see <http://www.gnu.org/licenses/>.
17"""
18Module for writing optimization and simulation results to file.
19"""
20from operator import itemgetter, attrgetter
21import array
22import codecs
23import re
24import sys
25
26import numpy as N
27import numpy as np
28import scipy.io
29
30import pyfmi.fmi as fmi
31import pyfmi.fmi_util as fmi_util
32from pyfmi.common import python3_flag, encode, decode
33
34SYS_LITTLE_ENDIAN = sys.byteorder == 'little'
35
36class Trajectory:
37    """
38    Class for storing a time series.
39    """
40   
41    def __init__(self,t,x):
42        """
43        Constructor for the Trajectory class.
44
45        Parameters::
46       
47            t --
48                Abscissa of the trajectory.
49            x --
50                The ordinate of the trajectory.
51
52        """
53        self.t = t
54        self.x = x
55
56class ResultStorage:
57    pass
58   
59    def get_variable_data(self, key):
60        raise NotImplementedError
61       
62    def is_variable(self, var):
63        raise NotImplementedError
64       
65    def is_negated(self, var):
66        raise NotImplementedError
67   
68
69class ResultHandler:
70
71    def simulation_start(self):
72        """
73        This method is called before the simulation has started and
74        after the initialization call for the FMU.
75        """
76        pass
77       
78    def initialize_complete(self):
79        """
80        This method is called after the initialization method of the FMU
81        has been been called.
82        """
83        pass
84       
85    def integration_point(self, solver=None):
86        """
87        This method is called for each time-point for which result are
88        to be stored as indicated by the "number of communcation points"
89        provided to the simulation method.
90        """
91        pass
92       
93    def simulation_end(self):
94        """
95        This method is called at the end of a simulation.
96        """
97        pass
98   
99    def set_options(self, options):
100        """
101        Options are the options dictionary provided to the simulation
102        method.
103        """
104        pass
105       
106    def get_result(self):
107        """
108        Method for retrieving the result. This method should return a
109        result of an instance of ResultStorage or of an instance of a
110        subclass of ResultStorage.
111        """
112        raise NotImplementedError
113
114class ResultDymola:
115    """
116    Base class for representation of a result file.
117    """
118    def _get_name(self):
119        return [decode(n) for n in self._name]
120   
121    name = property(fget = _get_name)
122   
123    def get_variable_index(self,name): 
124        """
125        Retrieve the index in the name vector of a given variable.
126       
127        Parameters::
128       
129            name --
130                Name of variable.
131
132        Returns::
133       
134            In integer index.
135        """
136        #Strip name of spaces, for instace a[2, 1] to a[2,1]
137        name = name.replace(" ", "")
138       
139        try:
140            if python3_flag and isinstance(self, ResultDymolaBinary):
141                return self.name_lookup[encode(name)]
142            else:
143                return self.name_lookup[name]
144        except KeyError as ex:
145            #Variable was not found so check if it was a derivative variable
146            #and check if there exists a variable with another naming
147            #convention
148            if self._check_if_derivative_variable(name):
149                try:
150                    #First do a simple search for the other naming convention
151                    if python3_flag and isinstance(self, ResultDymolaBinary):
152                        return self.name_lookup[encode(self._convert_dx_name(name))]
153                    else:
154                        return self.name_lookup[self._convert_dx_name(name)]
155                except KeyError as ex:
156                    return self._exhaustive_search_for_derivatives(name)
157            else:
158                raise VariableNotFoundError("Cannot find variable " +
159                                        name + " in data file.")
160           
161   
162    def _check_if_derivative_variable(self, name):
163        """
164        Check if a variable is a derivative variable or not.
165        """
166        if name.startswith("der(") or name.split(".")[-1].startswith("der("):
167            return True
168        else:
169            return False
170       
171   
172    def _exhaustive_search_for_derivatives(self, name):
173        """
174        Perform an exhaustive search for a derivative variable by
175        first retrieving the underlying state and for each its alias
176        check if there exists a derivative variable.
177        """
178        #Find alias for name
179        state = self._find_underlying_state(name)
180        index = self.get_variable_index(state)
181       
182        alias_index = N.where(self.dataInfo[:,1]==self.dataInfo[index,1])[0]
183       
184        #Loop through all alias
185        for ind in alias_index:
186            #Get the trial name
187            trial_name = self._name[ind]
188           
189            #Create the derivative name
190            if python3_flag and isinstance(self, ResultDymolaBinary): 
191                der_trial_name = self._create_derivative_from_state(decode(trial_name))
192            else:
193                der_trial_name = self._create_derivative_from_state(trial_name)
194           
195            try:
196                if python3_flag and isinstance(self, ResultDymolaBinary):
197                    return self.name_lookup[encode(der_trial_name)]
198                else:
199                    return self.name_lookup[der_trial_name]
200            except KeyError as ex:
201                try:
202                    if python3_flag and isinstance(self, ResultDymolaBinary):
203                        return self.name_lookup[encode(self._convert_dx_name(der_trial_name))]
204                    else:
205                        return self.name_lookup[self._convert_dx_name(der_trial_name)]
206                except KeyError as ex:
207                    pass
208        else:
209            raise VariableNotFoundError("Cannot find variable " +
210                                        name + " in data file.")
211   
212    def _find_underlying_state(self, name):
213        """
214        Finds the underlying state of a derivative variable. der(PI.x)
215        -> PI.x.
216        """
217        spl = name.split(".")
218       
219        if spl[0].startswith("der("):
220            spl[0] = spl[0][4:] #Remove der(
221            spl[-1] = spl[-1][:-1] #Remove )
222            return ".".join(spl)
223        elif spl[-1].startswith("der("):
224            spl[-1] = spl[-1][4:] #Remove der(
225            spl[-1] = spl[-1][:-1] #Remove )
226            return ".".join(spl)
227        else:
228            return name
229   
230    def _create_derivative_from_state(self, name):
231        """
232        Create a derivative variable from a state by adding for instance
233        to PI.x -> der(PI.x).
234        """
235        return "der("+name+")"
236   
237    def _convert_dx_name(self, name):
238        """
239        Internal method for converting the derivative variable into the
240        "other" convention. A derivative variable can either be on the
241        form PI.der(x) and der(PI.x).
242       
243        Returns the original name if the name was not a derivative name.
244        """
245        spl = name.split(".") #Split name
246       
247        if spl[0].startswith("der("): #der(PI.x)
248            spl[0] = spl[0][4:] #Remove der
249            spl[-1] = "der("+spl[-1] #Add der
250           
251            return ".".join(spl) #PI.der(x)
252
253        elif spl[-1].startswith("der("): #PI.der(x)
254            spl[0] = "der("+spl[0] #Add der
255            spl[-1] = spl[-1][4:] #Remove der
256           
257            return ".".join(spl)
258
259        else: #Variable was not a derivative variable
260            return name
261
262class ResultCSVTextual:
263    def __init__(self, filename, delimiter=";"):
264       
265        fid = codecs.open(filename,'r','utf-8')
266       
267        if delimiter == ";":
268            name = fid.readline().strip().split(delimiter)
269        elif delimiter == ",":
270            name = [s[1:-1] for s in re.findall('".+?"', fid.readline().strip())]
271        else:
272            raise JIOError('Unsupported separator.') 
273        self._name = name
274       
275        self.data_matrix = {}
276        for i,n in enumerate(name):
277            self.data_matrix[n] = i
278       
279        data = []
280        while True:
281            row = fid.readline().strip().split(delimiter)
282           
283            if row[-1] == "" or row[-1] == "\n":
284                break
285           
286            data.append([float(d) for d in row])
287           
288        self.data = N.array(data)
289       
290    def get_variable_data(self,name):
291        """
292        Retrieve the data sequence for a variable with a given name.
293       
294        Parameters::
295       
296            name --
297                Name of the variable.
298
299        Returns::
300       
301            A Trajectory object containing the time vector and the data vector
302            of the variable.
303        """
304        if name == 'time':
305            return Trajectory(self.data[:,0],self.data[:,0])
306        else:
307            return Trajectory(self.data[:,0],self.data[:,self.data_matrix[name]])
308               
309    def is_variable(self, name):
310        return True
311           
312    def is_negated(self, name):
313        return False
314       
315    def get_data_matrix(self):
316        """
317        Returns the result matrix.
318               
319        Returns::
320       
321            The result data matrix.
322        """
323        return self.data
324
325class ResultWriter():
326    """
327    Base class for writing results to file.
328    """
329   
330    def write_header():
331        """
332        The header is intended to be used for writing general information about
333        the model. This is intended to be called once.
334        """
335        pass
336       
337    def write_point():
338        """
339        This method does the writing of the actual result.
340        """
341        pass
342       
343    def write_finalize():
344        """
345        The finalize method can be used to for instance close the file.
346        """
347        pass
348
349class ResultWriterDymola(ResultWriter):
350    """
351    Export an optimization or simulation result to file in Dymola's result file
352    format.
353    """
354    def __init__(self, model, format='txt'):
355        """
356        Export an optimization or simulation result to file in Dymolas result
357        file format.
358
359        Parameters::
360       
361            model --
362                A FMIModel object.
363           
364            format --
365                A text string equal either to 'txt' for textual format or 'mat'
366                for binary Matlab format.
367                Default: 'txt'
368
369        Limitations::
370       
371            Currently only textual format is supported.
372        """
373        self.model = model
374       
375        if format!='txt':
376            raise JIOError('The format is currently not supported.')
377       
378        #Internal values
379        self._file_open = False
380        self._npoints = 0
381       
382   
383    def write_header(self, file_name='', parameters=None):
384        """
385        Opens the file and writes the header. This includes the information
386        about the variables and a table determining the link between variables
387        and data.
388       
389        Parameters::
390       
391            file_name --
392                If no file name is given, the name of the model (as defined by
393                FMUModel.get_identifier()) concatenated with the string '_result' is
394                used. A file suffix equal to the format argument is then
395                appended to the file name.
396                Default: Empty string.
397        """
398        if file_name=='':
399            file_name=self.model.get_identifier() + '_result.txt'
400
401        # Open file
402        f = codecs.open(file_name,'w','utf-8')
403        self._file_open = True
404       
405        # Write header
406        f.write('#1\n')
407        f.write('char Aclass(3,11)\n')
408        f.write('Atrajectory\n')
409        f.write('1.1\n')
410        f.write('\n')
411       
412        # all lists that we need for later
413        vrefs_alias = []
414        vrefs_noalias = []
415        vrefs = []
416        names_alias = []
417        names_noalias = []
418        names = []
419        aliases_alias = []
420        aliases = []
421        descriptions_alias = []
422        descriptions = []
423        variabilities_alias = []
424        variabilities_noalias = []
425        variabilities = []
426        types_alias = []
427        types_noalias = []
428        types = []
429       
430        for var in self.model.get_model_variables().values():
431            if not var.type == fmi.FMI_STRING and not var.type == fmi.FMI_ENUMERATION:
432                    if var.alias == fmi.FMI_NO_ALIAS:
433                        vrefs_noalias.append(var.value_reference)
434                        names_noalias.append(var.name)
435                        aliases.append(var.alias)
436                        descriptions.append(var.description)
437                        variabilities_noalias.append(var.variability)
438                        types_noalias.append(var.type)
439                    else:
440                        vrefs_alias.append(var.value_reference)
441                        names_alias.append(var.name)
442                        aliases_alias.append(var.alias)
443                        descriptions_alias.append(var.description)
444                        variabilities_alias.append(var.variability)
445                        types_alias.append(var.type)
446                       
447        # need to save these no alias lists for later
448        vrefs = vrefs_noalias[:]
449        names = names_noalias[:]
450        types = types_noalias[:]
451        variabilities = variabilities_noalias[:]
452       
453        # merge lists
454        vrefs.extend(vrefs_alias)
455        names.extend(names_alias)
456        aliases.extend(aliases_alias)
457        descriptions.extend(descriptions_alias)
458        variabilities.extend(variabilities_alias)
459        types.extend(types_alias)
460       
461        # zip to list of tuples and sort - non alias variables are now
462        # guaranteed to be first in list
463        names_noalias = sorted(zip(
464            tuple(vrefs_noalias), 
465            tuple(names_noalias)), 
466            key=itemgetter(0))
467        variabilities_noalias = sorted(zip(
468            tuple(vrefs_noalias), 
469            tuple(variabilities_noalias)), 
470            key=itemgetter(0))
471        types_noalias = sorted(zip(
472            tuple(vrefs_noalias), 
473            tuple(types_noalias)), 
474            key=itemgetter(0))
475        names = sorted(zip(
476            tuple(vrefs), 
477            tuple(names)), 
478            key=itemgetter(0))
479        aliases = sorted(zip(
480            tuple(vrefs), 
481            tuple(aliases)), 
482            key=itemgetter(0))
483        descriptions = sorted(zip(
484            tuple(vrefs), 
485            tuple(descriptions)), 
486            key=itemgetter(0))
487        variabilities = sorted(zip(
488            tuple(vrefs), 
489            tuple(variabilities)), 
490            key=itemgetter(0))
491        types = sorted(zip(
492            tuple(vrefs), 
493            tuple(types)), 
494            key=itemgetter(0))
495       
496        num_vars = len(names)
497       
498        names_sens = []
499        descs_sens = []
500        cont_vars = []
501       
502        if parameters != None:
503            vars = self.model.get_model_variables(type=0,include_alias=False,variability=3)
504            for i in self.model.get_state_value_references():
505                for j in vars.keys():
506                    if i == vars[j].value_reference:
507                        cont_vars.append(vars[j].name)
508       
509        if parameters != None:
510            for j in range(len(parameters)):
511                for i in range(len(self.model.continuous_states)):
512                    names_sens += ['d'+cont_vars[i]+'/d'+parameters[j]]
513                    descs_sens  += ['Sensitivity of '+cont_vars[i]+' with respect to '+parameters[j]+'.']
514
515        # Find the maximum name and description length
516        max_name_length = len('Time')
517        max_desc_length = len('Time in [s]')
518       
519        for i in range(len(names)):
520            name = names[i][1]
521            desc = descriptions[i][1]
522           
523            if (len(name)>max_name_length):
524                max_name_length = len(name)
525               
526            if (len(desc)>max_desc_length):
527                max_desc_length = len(desc)
528       
529        for i in range(len(names_sens)):
530            name = names_sens[i]
531            desc = descs_sens[i]
532           
533            if (len(name)>max_name_length):
534                max_name_length = len(name)
535               
536            if (len(desc)>max_desc_length):
537                max_desc_length = len(desc)
538
539        f.write('char name(%d,%d)\n' % (num_vars+len(names_sens)+1, max_name_length))
540        f.write('time\n')
541
542        for name in names:
543            f.write(name[1] +'\n')
544        for name in names_sens:
545            f.write(name + '\n')
546
547        f.write('\n')
548
549        # Write descriptions       
550        f.write('char description(%d,%d)\n' % (num_vars+len(names_sens) + 1, max_desc_length))
551        f.write('Time in [s]\n')
552
553        # Loop over all variables, not only those with a description
554        for desc in descriptions:
555            f.write(desc[1] +'\n')
556        for desc in descs_sens:
557            f.write(desc + '\n')
558               
559        f.write('\n')
560
561        # Write data meta information
562       
563        f.write('int dataInfo(%d,%d)\n' % (num_vars+len(names_sens) + 1, 4))
564        f.write('0 1 0 -1 # time\n')
565       
566        list_of_continuous_states = N.append(self.model._save_real_variables_val, 
567            self.model._save_int_variables_val)
568        list_of_continuous_states = N.append(list_of_continuous_states, 
569            self.model._save_bool_variables_val).tolist()
570        list_of_continuous_states = dict(zip(list_of_continuous_states, 
571            range(len(list_of_continuous_states))))
572        valueref_of_continuous_states = []
573       
574        cnt_1 = 1
575        cnt_2 = 1
576        n_parameters = 0
577        datatable1 = False
578        for i, name in enumerate(names):
579            if aliases[i][1] == 0: # no alias
580                if variabilities[i][1] == fmi.FMI_PARAMETER or \
581                    variabilities[i][1] == fmi.FMI_CONSTANT:
582                    cnt_1 += 1
583                    n_parameters += 1
584                    f.write('1 %d 0 -1 # ' % cnt_1 + name[1]+'\n')
585                    datatable1 = True
586                else:
587                    cnt_2 += 1
588                    valueref_of_continuous_states.append(
589                        list_of_continuous_states[name[0]])
590                    f.write('2 %d 0 -1 # ' % cnt_2 + name[1] +'\n')
591                    datatable1 = False
592               
593            elif aliases[i][1] == 1: # alias
594                if datatable1:
595                    f.write('1 %d 0 -1 # ' % cnt_1 + name[1]+'\n')
596                else:
597                    f.write('2 %d 0 -1 # ' % cnt_2 + name[1] +'\n')
598            else:
599                if datatable1:
600                    f.write('1 -%d 0 -1 # ' % cnt_1 + name[1]+'\n')
601                else:
602                    f.write('2 -%d 0 -1 # ' % cnt_2 + name[1] +'\n')
603        for i, name in enumerate(names_sens):
604            cnt_2 += 1
605            f.write('2 %d 0 -1 # ' % cnt_2 + name +'\n')
606       
607       
608        f.write('\n')
609
610        # Write data
611        # Write data set 1
612        f.write('float data_1(%d,%d)\n' % (2, n_parameters + 1))
613        f.write("%.14E" % self.model.time)
614        str_text = ''
615       
616        # write constants and parameters
617        for i, name in enumerate(names_noalias):
618            if variabilities_noalias[i][1] == fmi.FMI_CONSTANT or \
619                variabilities_noalias[i][1] == fmi.FMI_PARAMETER:
620                    if types_noalias[i][1] == fmi.FMI_REAL:
621                        str_text = str_text + (
622                            " %.14E" % (self.model.get_real([name[0]])))
623                    elif types_noalias[i][1] == fmi.FMI_INTEGER:
624                        str_text = str_text + (
625                            " %.14E" % (self.model.get_integer([name[0]])))
626                    elif types_noalias[i][1] == fmi.FMI_BOOLEAN:
627                        str_text = str_text + (
628                            " %.14E" % (float(
629                                self.model.get_boolean([name[0]])[0])))
630                       
631        f.write(str_text)
632        f.write('\n')
633        self._point_last_t = f.tell()
634        f.write("%s" % ' '*28)
635        f.write(str_text)
636
637        f.write('\n\n')
638       
639        self._nvariables = len(valueref_of_continuous_states)+1
640        self._nvariables_sens = len(names_sens)
641       
642       
643        f.write('float data_2(')
644        self._point_npoints = f.tell()
645        f.write(' '*(14+4+14))
646        f.write('\n')
647       
648        #f.write('%s,%d)\n' % (' '*14, self._nvariables))
649       
650        self._file = f
651        self._data_order = valueref_of_continuous_states
652       
653    def write_point(self, data=None, parameter_data=[]):
654        """
655        Writes the current status of the model to file. If the header has not
656        been written previously it is written now. If data is specified it is
657        written instead of the current status.
658       
659        Parameters::
660           
661                data --
662                    A one dimensional array of variable trajectory data. data
663                    should consist of information about the status in the order
664                    specified by FMUModel.save_time_point()
665                    Default: None
666        """
667        f = self._file
668        data_order = self._data_order
669
670        #If data is none, store the current point from the model
671        if data==None:
672            #Retrieves the time-point
673            [r,i,b] = self.model.save_time_point()
674            data = N.append(N.append(N.append(self.model.time,r),i),b)
675
676        #Write the point
677        str_text = (" %.14E" % data[0])
678        for j in range(self._nvariables-1):
679            str_text = str_text + (" %.14E" % (data[1+data_order[j]]))
680        for j in range(len(parameter_data)):
681            str_text = str_text + (" %.14E" % (parameter_data[j]))
682        f.write(str_text+'\n')
683       
684        #Update number of points
685        self._npoints+=1
686
687    def write_finalize(self):
688        """
689        Finalize the writing by filling in the blanks in the created file. The
690        blanks consists of the number of points and the final time (in data set
691        1). Also closes the file.
692        """
693        #If open, finalize and close
694        if self._file_open:
695           
696            f = self._file
697           
698            f.seek(self._point_last_t)
699           
700            f.write('%.14E'%self.model.time)
701           
702            f.seek(self._point_npoints)
703            f.write('%d,%d)' % (self._npoints, self._nvariables+self._nvariables_sens))
704            #f.write('%d'%self._npoints)
705            f.seek(-1,2)
706            #Close the file
707            f.write('\n')
708            f.close()
709            self._file_open = False
710         
711
712
713class ResultStorageMemory(ResultDymola): 
714    """ 
715    Class representing a simulation result that is kept in MEMORY.
716    """ 
717   
718    def __init__(self, model, data, vars_ref, vars): 
719        """
720        Load result from the ResultHandlerMemory
721   
722        Parameters::
723                 
724            model --
725                Instance of the FMUModel.
726            data --
727                The simulation data.
728        """             
729        self.model = model
730        self.vars = vars 
731        self._name = [var.name for var in vars.values()] 
732        self.data = {}
733        self.data_matrix = data
734           
735        #time real integer boolean
736        real_val_ref    = vars_ref[0]
737        integer_val_ref = vars_ref[1]
738        boolean_val_ref = vars_ref[2]
739           
740        self.time = data[:,0] 
741        for i,ref in enumerate(real_val_ref+integer_val_ref+boolean_val_ref): 
742            self.data[ref] = data[:,i+1] 
743           
744    def get_variable_data(self,name): 
745        """
746        Retrieve the data sequence for a variable with a given name.
747           
748        Parameters::
749           
750            name --
751                Name of the variable.
752   
753        Returns::
754           
755            A Trajectory object containing the time vector and the data vector 
756            of the variable.
757        """ 
758        if name == 'time': 
759            return Trajectory(self.time,self.time) 
760        else: 
761            try: 
762                var = self.vars[name] 
763            except KeyError as ex: 
764                raise VariableNotFoundError("Cannot find variable " + 
765                                        name + " in data file.") 
766                                           
767            factor = -1 if var.alias == fmi.FMI_NEGATED_ALIAS else 1 
768               
769            if var.variability == fmi.FMI_CONSTANT or var.variability == fmi.FMI_PARAMETER: 
770                return Trajectory([self.time[0],self.time[-1]],N.array([self.model.get(name),self.model.get(name)]).ravel()) 
771            else: 
772                return Trajectory(self.time,factor*self.data[var.value_reference]) 
773   
774                   
775    def is_variable(self, name): 
776        """
777        Returns True if the given name corresponds to a time-varying variable.
778           
779        Parameters::
780           
781            name -- 
782                Name of the variable/parameter/constant.
783                   
784        Returns::
785           
786            True if the variable is time-varying.
787        """ 
788        if name == 'time': 
789            return True 
790        variability = self.vars[name].variability
791           
792        if variability ==  fmi.FMI_CONSTANT or variability == fmi.FMI_PARAMETER: 
793            return False 
794        else: 
795            return True 
796               
797    def is_negated(self, name): 
798        """
799        Returns True if the given name corresponds to a negated result vector.
800           
801        Parameters::
802           
803            name -- 
804                Name of the variable/parameter/constant.
805                   
806        Returns::
807           
808            True if the result should be negated
809        """ 
810        alias = self.vars[name].alias
811           
812        if alias == fmi.FMI_NEGATED_ALIAS: 
813            return True 
814        else: 
815            return False 
816       
817    def get_column(self, name): 
818        """
819        Returns the column number in the data matrix where the values of the 
820        variable are stored.
821           
822        Parameters::
823           
824            name -- 
825                Name of the variable/parameter/constant.
826               
827        Returns::
828           
829            The column number.
830        """ 
831        raise NotImplementedError 
832       
833    def get_data_matrix(self): 
834        """
835        Returns the result matrix.
836                   
837        Returns::
838           
839            The result data matrix.
840        """ 
841        return self.data_matrix
842
843class ResultDymolaTextual(ResultDymola):
844    """
845    Class representing a simulation or optimization result loaded from a Dymola
846    binary file.
847    """
848
849    def __init__(self,fname):
850        """
851        Load a result file written on Dymola textual format.
852
853        Parameters::
854       
855            fname --
856                Name of file.
857        """
858        fid = codecs.open(fname,'r','utf-8')
859       
860        result  = []
861     
862        # Read Aclass section
863        nLines = self._find_phrase(fid, 'char Aclass')
864
865        nLines = int(nLines[0])
866        Aclass = [fid.readline().strip() for i in range(nLines)]
867        #Aclass = []
868        #for i in range(0,nLines):
869        #    Aclass.append(fid.readline().strip())
870        self.Aclass = Aclass
871
872        # Read name section
873        nLines = self._find_phrase(fid, 'char name')
874
875        nLines = int(nLines[0])
876        name = [fid.readline().strip().replace(" ","") for i in range(nLines)]
877        #name = []
878        #for i in range(0,nLines):
879        #    name.append(fid.readline().strip().replace(" ",""))
880        self._name = name
881        self.name_lookup = {key:ind for ind,key in enumerate(self._name)}
882     
883        # Read description section 
884        nLines = self._find_phrase(fid, 'char description') 
885
886        nLines = int(nLines[0])
887        description = [fid.readline().strip() for i in range(nLines)]
888        #description = []
889        #for i in range(0,nLines):
890        #    description.append(fid.readline().strip())
891        self.description = description
892
893        # Read dataInfo section
894        nLines = self._find_phrase(fid, 'int dataInfo')
895
896        nCols = nLines[2].partition(')')
897        nLines = int(nLines[0])
898        nCols = int(nCols[0])
899       
900        if python3_flag:
901            dataInfo = [list(map(int,fid.readline().split()[0:nCols])) for i in range(nLines)]
902        else:
903            dataInfo = [map(int,fid.readline().split()[0:nCols]) for i in range(nLines)]
904        self.dataInfo = N.array(dataInfo)
905
906        # Find out how many data matrices there are
907        nData = max(self.dataInfo[:,0])
908               
909        self.data = []
910        for i in range(0,nData): 
911            l = fid.readline()
912            tmp = l.partition(' ')
913            while tmp[0]!='float' and tmp[0]!='double' and l!='':
914                l = fid.readline()
915                tmp = l. partition(' ')
916            if l=='':
917                raise JIOError('The result does not seem to be of a supported format.')
918            tmp = tmp[2].partition('(')
919            nLines = tmp[2].partition(',')
920            nCols = nLines[2].partition(')')
921            nLines = int(nLines[0])
922            nCols = int(nCols[0])
923            data = []
924            for i in range(0,nLines):
925                info = []
926                while len(info) < nCols and l != '':
927                    l = fid.readline()
928                    info.extend(l.split())
929                try:
930                    if python3_flag:
931                        data.append(list(map(float,info[0:nCols])))
932                    else:
933                        data.append(map(float,info[0:nCols]))
934                except ValueError: #Handle 1.#INF's and such
935                    if python3_flag:
936                        data.append(list(map(robust_float,info[0:nCols])))
937                    else:
938                        data.append(map(robust_float,info[0:nCols]))
939                if len(info) == 0 and i < nLines-1:
940                    raise JIOError("Inconsistent number of lines in the result data.")
941                del(info)
942            self.data.append(N.array(data))
943           
944        if len(self.data) == 0:
945            raise JIOError('Could not find any variable data in the result file.')
946           
947    def _find_phrase(self,fid, phrase):
948        l = fid.readline()
949        tmp = l.partition('(')
950        while tmp[0]!=phrase and l!='':
951            l = fid.readline()
952            tmp = l. partition('(')
953        if l=='':
954            raise JIOError("The result does not seem to be of a supported format.")
955        return tmp[2].partition(',')
956
957    def get_variable_data(self,name):
958        """
959        Retrieve the data sequence for a variable with a given name.
960       
961        Parameters::
962       
963            name --
964                Name of the variable.
965
966        Returns::
967       
968            A Trajectory object containing the time vector and the data vector
969            of the variable.
970        """
971        if name == 'time' or name== 'Time':
972            varInd = 0
973        else:
974            varInd  = self.get_variable_index(name)
975           
976        dataInd = self.dataInfo[varInd][1]
977        factor = 1
978        if dataInd < 0:
979            factor = -1
980            dataInd = -dataInd -1
981        else:
982            dataInd = dataInd - 1
983        dataMat = self.dataInfo[varInd][0]-1
984       
985        if dataMat < 0:
986            # Take into account that the 'Time' variable has data matrix index 0
987            # and that 'time' is called 'Time' in Dymola results
988             dataMat = 1 if len(self.data) > 1 else 0
989             
990        return Trajectory(
991            self.data[dataMat][:,0],factor*self.data[dataMat][:,dataInd])
992       
993    def is_variable(self, name):
994        """
995        Returns True if the given name corresponds to a time-varying variable.
996       
997        Parameters::
998       
999            name --
1000                Name of the variable/parameter/constant
1001               
1002        Returns::
1003       
1004            True if the variable is time-varying.
1005        """
1006        if name == 'time' or name== 'Time':
1007            return True
1008        varInd  = self.get_variable_index(name)
1009        dataMat = self.dataInfo[varInd][0]-1
1010        if dataMat<0:
1011            dataMat = 0
1012       
1013        if dataMat == 0:
1014            return False
1015        else:
1016            return True
1017           
1018    def is_negated(self, name):
1019        """
1020        Returns True if the given name corresponds to a negated result vector.
1021       
1022        Parameters::
1023       
1024            name --
1025                Name of the variable/parameter/constant.
1026               
1027        Returns::
1028       
1029            True if the result should be negated
1030        """
1031        varInd  = self.get_variable_index(name)
1032        dataInd = self.dataInfo[varInd][1]
1033        if dataInd<0:
1034            return True
1035        else:
1036            return False
1037   
1038    def get_column(self, name):
1039        """
1040        Returns the column number in the data matrix where the values of the
1041        variable are stored.
1042       
1043        Parameters::
1044       
1045            name --
1046                Name of the variable/parameter/constant.
1047           
1048        Returns::
1049       
1050            The column number.
1051        """
1052        if name == 'time' or name== 'Time':
1053            return 0
1054
1055        if not self.is_variable(name):
1056            raise VariableNotTimeVarying("Variable " +
1057                                        name + " is not time-varying.")
1058        varInd  = self.get_variable_index(name)
1059        dataInd = self.dataInfo[varInd][1]
1060        factor = 1
1061        if dataInd<0:
1062            factor = -1
1063            dataInd = -dataInd -1
1064        else:
1065            dataInd = dataInd - 1
1066           
1067        return dataInd
1068       
1069    def get_data_matrix(self):
1070        """
1071        Returns the result matrix.
1072       
1073        Returns::
1074       
1075            The result data matrix.
1076        """
1077        return self.data[1]
1078
1079    def shift_time(self,time_shift):
1080        """
1081        Shift the time vector using a fixed offset.
1082
1083        Parameters::
1084            time_shift --
1085                The time shift offset.
1086        """
1087        for i in range(len(self.data)):
1088            for j in range(N.shape(self.data[i])[0]):
1089                self.data[i][j,0] = self.data[i][j,0] + time_shift
1090
1091    def append(self, res):
1092        """
1093        Append another simulation result. The time vector of the appended
1094        trajectories is shifted so that the appended trajectories appears
1095        after the original result trajectories.
1096
1097        Parameters::
1098            res --
1099                A simulation result object of type DymolaResultTextual.
1100        """
1101        n_points = N.size(res.data[1],0)
1102        time_shift = self.data[1][-1,0]
1103        self.data[1] = N.vstack((self.data[1],res.data[1]))
1104        self.data[1][n_points:,0] = self.data[1][n_points:,0] + time_shift
1105
1106class ResultDymolaBinary(ResultDymola):
1107    """
1108    Class representing a simulation or optimization result loaded from a Dymola
1109    binary file.
1110    """
1111
1112    def __init__(self,fname):
1113        """
1114        Load a result file written on Dymola binary format.
1115
1116        Parameters::
1117       
1118            fname --
1119                Name of file.
1120        """
1121        self._fname = fname
1122        self.raw = scipy.io.loadmat(fname,chars_as_strings=False, variable_names=["name", "dataInfo", "data_1", "data_2"])
1123        name = self.raw['name']
1124        self.raw_name = name
1125
1126        self._name = fmi_util.convert_array_names_list_names_int(name.view(np.int32))
1127        self.dataInfo = self.raw['dataInfo'].transpose()
1128        self.name_lookup = {key:ind for ind,key in enumerate(self._name)}
1129       
1130        self._description = None 
1131   
1132    def _get_description(self):
1133        if not self._description:
1134            description = scipy.io.loadmat(self._fname,chars_as_strings=False, variable_names=["description"])["description"]
1135            self._description = ["".join(description[:,i]).rstrip() for i in range(description[0,:].size)]
1136       
1137        return self._description
1138
1139    description = property(_get_description, doc = 
1140    """
1141    Property for accessing the description vector.
1142    """)
1143       
1144    def get_variable_data(self,name):
1145        """
1146        Retrieve the data sequence for a variable with a given name.
1147       
1148        Parameters::
1149       
1150            name --
1151                Name of the variable.
1152
1153        Returns::
1154       
1155            A Trajectory object containing the time vector and the data vector
1156            of the variable.
1157        """
1158        if python3_flag and isinstance(name, bytes):
1159            name = decode(name)
1160           
1161        if name == 'time' or name== 'Time':
1162            varInd = 0;
1163        else:
1164            varInd  = self.get_variable_index(name)
1165           
1166        dataInd = self.raw['dataInfo'][1][varInd]
1167        dataMat = self.raw['dataInfo'][0][varInd]
1168        factor = 1
1169        if dataInd<0:
1170            factor = -1
1171            dataInd = -dataInd -1
1172        else:
1173            dataInd = dataInd - 1
1174           
1175        if dataMat == 0:
1176            # Take into account that the 'Time' variable has data matrix index 0
1177            # and that 'time' is called 'Time' in Dymola results
1178            dataMat = 2 if len(self.raw['data_2'])> 0 else 1
1179               
1180        return Trajectory(self.raw['data_%d'%dataMat][0,:],factor*self.raw['data_%d'%dataMat][dataInd,:])
1181
1182    def is_variable(self, name):
1183        """
1184        Returns True if the given name corresponds to a time-varying variable.
1185       
1186        Parameters::
1187       
1188            name --
1189                Name of the variable/parameter/constant.
1190               
1191        Returns::
1192       
1193            True if the variable is time-varying.
1194        """
1195        if name == 'time' or name== 'Time':
1196            return True
1197        varInd  = self.get_variable_index(name)
1198        dataMat = self.raw['dataInfo'][0][varInd]
1199        if dataMat<1:
1200            dataMat = 1
1201       
1202        if dataMat == 1:
1203            return False
1204        else:
1205            return True
1206           
1207    def is_negated(self, name):
1208        """
1209        Returns True if the given name corresponds to a negated result vector.
1210       
1211        Parameters::
1212       
1213            name --
1214                Name of the variable/parameter/constant.
1215               
1216        Returns::
1217       
1218            True if the result should be negated
1219        """
1220        varInd  = self.get_variable_index(name)
1221        dataInd = self.raw['dataInfo'][1][varInd]
1222        if dataInd<0:
1223            return True
1224        else:
1225            return False
1226   
1227    def get_column(self, name):
1228        """
1229        Returns the column number in the data matrix where the values of the
1230        variable are stored.
1231       
1232        Parameters::
1233       
1234            name --
1235                Name of the variable/parameter/constant.
1236           
1237        Returns::
1238       
1239            The column number.
1240        """
1241        if name == 'time' or name== 'Time':
1242            return 0
1243       
1244        if not self.is_variable(name):
1245            raise VariableNotTimeVarying("Variable " +
1246                                        name + " is not time-varying.")
1247        varInd  = self.get_variable_index(name)
1248        dataInd = self.raw['dataInfo'][1][varInd]
1249        factor = 1
1250        if dataInd<0:
1251            factor = -1
1252            dataInd = -dataInd -1
1253        else:
1254            dataInd = dataInd - 1
1255           
1256        return dataInd
1257   
1258    def get_data_matrix(self):
1259        """
1260        Returns the result matrix.
1261               
1262        Returns::
1263       
1264            The result data matrix.
1265        """
1266        return self.raw['data_%d'%2]
1267       
1268
1269
1270class ResultHandlerMemory(ResultHandler):
1271    def __init__(self, model):
1272        self.model = model
1273       
1274    def simulation_start(self):
1275        """
1276        This method is called before the simulation has started and before
1277        the initialization of the model.
1278        """
1279        model = self.model
1280        opts = self.options
1281       
1282        self.vars = model.get_model_variables(filter=opts["filter"])
1283       
1284        #Store the continuous and discrete variables for result writing
1285        self.real_var_ref, self.int_var_ref, self.bool_var_ref = model.get_model_time_varying_value_references(filter=opts["filter"])
1286           
1287        self.real_sol = []
1288        self.int_sol  = []
1289        self.bool_sol = []
1290        self.time_sol = []
1291        self.param_sol= []
1292       
1293        self.model = model
1294       
1295    def initialize_complete(self):
1296        """
1297        This method is called after the initialization method of the FMU
1298        has been been called.
1299        """
1300        pass
1301       
1302    def integration_point(self, solver = None):
1303        """
1304        This method is called for each time-point for which result are
1305        to be stored as indicated by the "number of communcation points"
1306        provided to the simulation method.
1307        """
1308        model = self.model
1309       
1310        #Retrieves the time-point
1311        self.time_sol += [model.time]
1312        self.real_sol += [model.get_real(self.real_var_ref)]
1313        self.int_sol  += [model.get_integer(self.int_var_ref)]
1314        self.bool_sol += [model.get_boolean(self.bool_var_ref)]
1315       
1316        #Sets the parameters, if any
1317        if solver and self.options["sensitivities"]:
1318            self.param_sol += [N.array(solver.interpolate_sensitivity(model.time, 0)).flatten()]
1319       
1320    def simulation_end(self):
1321        """
1322        The finalize method can be used to for instance close the file.
1323        ANd this method is called after the simulation has completed.
1324        """
1325        pass
1326       
1327    def get_result(self):
1328        """
1329        Method for retrieving the result. This method should return a
1330        result of an instance of ResultBase or of an instance of a
1331        subclass of ResultBase.
1332        """
1333        t = N.array(self.time_sol) 
1334        r = N.array(self.real_sol) 
1335        data = N.c_[t,r] 
1336       
1337        if len(self.int_sol) > 0 and len(self.int_sol[0]) > 0: 
1338            i = N.array(self.int_sol) 
1339            data = N.c_[data,i] 
1340        if len(self.bool_sol) > 0 and len(self.bool_sol[0]) > 0: 
1341            b = N.array(self.bool_sol) 
1342            data = N.c_[data,b] 
1343
1344        return ResultStorageMemory(self.model, data, [self.real_var_ref,self.int_var_ref,self.bool_var_ref], self.vars)
1345       
1346    def set_options(self, options):
1347        """
1348        Options are the options dictionary provided to the simulation
1349        method.
1350        """
1351        self.options = options
1352
1353class ResultHandlerCSV(ResultHandler):
1354    def __init__(self, model, delimiter=";"):
1355        self.model = model
1356        self.delimiter = delimiter
1357   
1358    def initialize_complete(self):
1359        pass
1360   
1361    def simulation_start(self):
1362        """
1363        Opens the file and writes the header. This includes the information
1364        about the variables and a table determining the link between variables
1365        and data.
1366        """
1367        opts = self.options
1368        model = self.model
1369       
1370        #Internal values
1371        self.file_open = False
1372        self.nbr_points = 0
1373        delimiter = self.delimiter
1374       
1375        self.file_name = opts["result_file_name"]
1376        try:
1377            self.parameters = opts["sensitivities"]
1378        except KeyError:
1379            self.parameters = None
1380       
1381        if self.file_name == "":
1382            self.file_name=self.model.get_identifier() + '_result.csv'
1383       
1384        vars = model.get_model_variables(filter=opts["filter"])
1385       
1386        const_valref_real = []
1387        const_name_real = []
1388        const_alias_real = []
1389        const_valref_int = []
1390        const_name_int = []
1391        const_alias_int = []
1392        const_valref_bool = []
1393        const_name_bool = []
1394        const_alias_bool = []
1395        cont_valref_real = []
1396        cont_name_real = []
1397        cont_alias_real = []
1398        cont_valref_int = []
1399        cont_name_int = []
1400        cont_alias_int = []
1401        cont_valref_bool = []
1402        cont_name_bool = []
1403        cont_alias_bool = []
1404       
1405        for name in vars.keys():
1406            var = vars[name]
1407            if var.type == fmi.FMI_REAL:
1408                if var.variability == fmi.FMI_CONSTANT or var.variability == fmi.FMI_PARAMETER:
1409                    const_valref_real.append(var.value_reference)
1410                    const_name_real.append(var.name)
1411                    const_alias_real.append(-1 if var.alias == fmi.FMI_NEGATED_ALIAS else 1)
1412                else:
1413                    cont_valref_real.append(var.value_reference)
1414                    cont_name_real.append(var.name)
1415                    cont_alias_real.append(-1 if var.alias == fmi.FMI_NEGATED_ALIAS else 1)
1416            elif var.type == fmi.FMI_INTEGER or var.type == fmi.FMI_ENUMERATION:
1417                if var.variability == fmi.FMI_CONSTANT or var.variability == fmi.FMI_PARAMETER:
1418                    const_valref_int.append(var.value_reference)
1419                    const_name_int.append(var.name)
1420                    const_alias_int.append(-1 if var.alias == fmi.FMI_NEGATED_ALIAS else 1)
1421                else:
1422                    cont_valref_int.append(var.value_reference)
1423                    cont_name_int.append(var.name)
1424                    cont_alias_int.append(-1 if var.alias == fmi.FMI_NEGATED_ALIAS else 1)
1425            elif var.type == fmi.FMI_BOOLEAN:
1426                if var.variability == fmi.FMI_CONSTANT or var.variability == fmi.FMI_PARAMETER:
1427                    const_valref_bool.append(var.value_reference)
1428                    const_name_bool.append(var.name)
1429                    const_alias_bool.append(-1 if var.alias == fmi.FMI_NEGATED_ALIAS else 1)
1430                else:
1431                    cont_valref_bool.append(var.value_reference)
1432                    cont_name_bool.append(var.name)
1433                    cont_alias_bool.append(-1 if var.alias == fmi.FMI_NEGATED_ALIAS else 1)
1434       
1435        # Open file
1436        f = codecs.open(self.file_name,'w','utf-8')
1437        self.file_open = True
1438       
1439        if delimiter == ",":
1440            name_str = '"time"'
1441            for name in const_name_real+const_name_int+const_name_bool+cont_name_real+cont_name_int+cont_name_bool:
1442                name_str += delimiter+'"'+name+'"'
1443        else:
1444            name_str = "time"
1445            for name in const_name_real+const_name_int+const_name_bool+cont_name_real+cont_name_int+cont_name_bool:
1446                name_str += delimiter+name
1447           
1448        f.write(name_str+"\n")
1449       
1450        const_val_real    = model.get_real(const_valref_real)
1451        const_val_int     = model.get_integer(const_valref_int)
1452        const_val_bool    = model.get_boolean(const_valref_bool)
1453       
1454        const_str = ""
1455        for i,val in enumerate(const_val_real):
1456            const_str += "%.14E"%(const_alias_real[i]*val)+delimiter
1457        for i,val in enumerate(const_val_int):
1458            const_str += "%.14E"%(const_alias_int[i]*val)+delimiter
1459        for i,val in enumerate(const_val_bool):
1460            const_str += "%.14E"%(const_alias_bool[i]*val)+delimiter
1461           
1462        #for val in N.append(const_val_real,N.append(const_val_int,const_val_boolean)):
1463        #    const_str += "%.14E"%val+delimiter
1464        self.const_str = const_str
1465       
1466        self._file = f
1467       
1468        self.cont_valref_real = cont_valref_real
1469        self.cont_alias_real  = N.array(cont_alias_real)
1470        self.cont_valref_int  = cont_valref_int
1471        self.cont_alias_int  = N.array(cont_alias_int)
1472        self.cont_valref_bool = cont_valref_bool
1473        self.cont_alias_bool  = N.array(cont_alias_bool)
1474       
1475    def integration_point(self, solver = None):
1476        """
1477        Writes the current status of the model to file. If the header has not
1478        been written previously it is written now. If data is specified it is
1479        written instead of the current status.
1480       
1481        Parameters::
1482           
1483                data --
1484                    A one dimensional array of variable trajectory data. data
1485                    should consist of information about the status in the order
1486                    specified by FMUModel.save_time_point()
1487                    Default: None
1488        """
1489        f = self._file
1490        model = self.model
1491        delimiter = self.delimiter
1492
1493        #Retrieves the time-point
1494        t = model.time
1495        r = model.get_real(self.cont_valref_real)*self.cont_alias_real
1496        i = model.get_integer(self.cont_valref_int)*self.cont_alias_int
1497        b = model.get_boolean(self.cont_valref_bool)*self.cont_alias_bool
1498       
1499        data = N.append(N.append(r,i),b)
1500       
1501        cont_str = ""
1502        for val in data:
1503            cont_str += "%.14E%s"%(val,delimiter)
1504           
1505        f.write("%.14E%s"%(t,delimiter))
1506        if len(cont_str) == 0:
1507            f.write(self.const_str[:-1]+"\n")
1508        else:
1509            f.write(self.const_str)
1510            f.write(cont_str[:-1]+"\n")
1511       
1512
1513    def simulation_end(self):
1514        """
1515        Finalize the writing by filling in the blanks in the created file. The
1516        blanks consists of the number of points and the final time (in data set
1517        1). Also closes the file.
1518        """
1519        #If open, finalize and close
1520        if self.file_open:
1521            self._file.close()
1522            self.file_open = False
1523           
1524    def get_result(self):
1525        """
1526        Method for retrieving the result. This method should return a
1527        result of an instance of ResultBase or of an instance of a
1528        subclass of ResultBase.
1529        """
1530        return ResultCSVTextual(self.file_name, self.delimiter)
1531       
1532    def set_options(self, options):
1533        """
1534        Options are the options dictionary provided to the simulation
1535        method.
1536        """
1537        self.options = options
1538
1539class ResultHandlerFile(ResultHandler):
1540    """
1541    Export an optimization or simulation result to file in Dymola's result file
1542    format.
1543    """
1544    def __init__(self, model):
1545        self.model = model
1546   
1547    def initialize_complete(self):
1548        pass 
1549   
1550    def simulation_start(self):
1551        """
1552        Opens the file and writes the header. This includes the information
1553        about the variables and a table determining the link between variables
1554        and data.
1555        """
1556        opts = self.options
1557        model = self.model
1558       
1559        #Internal values
1560        self.file_open = False
1561        self.nbr_points = 0
1562       
1563        self.file_name = opts["result_file_name"]
1564        try:
1565            self.parameters = opts["sensitivities"]
1566        except KeyError:
1567            self.parameters = None
1568       
1569        if self.file_name == "":
1570            self.file_name=self.model.get_identifier() + '_result.txt'
1571           
1572        #Store the continuous and discrete variables for result writing
1573        self.real_var_ref, self.int_var_ref, self.bool_var_ref = model.get_model_time_varying_value_references(filter=opts["filter"])
1574       
1575        file_name = self.file_name
1576        parameters = self.parameters
1577       
1578        # Open file
1579        f = codecs.open(file_name,'w','utf-8')
1580        self.file_open = True
1581       
1582        # Write header
1583        f.write('#1\n')
1584        f.write('char Aclass(3,11)\n')
1585        f.write('Atrajectory\n')
1586        f.write('1.1\n')
1587        f.write('\n')
1588       
1589        # all lists that we need for later
1590        vrefs_alias = []
1591        vrefs_noalias = []
1592        vrefs = []
1593        names_alias = []
1594        names_noalias = []
1595        names = []
1596        aliases_alias = []
1597        aliases = []
1598        descriptions_alias = []
1599        descriptions = []
1600        variabilities_alias = []
1601        variabilities_noalias = []
1602        variabilities = []
1603        types_alias = []
1604        types_noalias = []
1605        types = []
1606       
1607        for var in self.model.get_model_variables(filter=self.options["filter"]).values():
1608            if not var.type == fmi.FMI_STRING:
1609                    if var.alias == fmi.FMI_NO_ALIAS:
1610                        vrefs_noalias.append(var.value_reference)
1611                        names_noalias.append(var.name)
1612                        aliases.append(var.alias)
1613                        descriptions.append(var.description)
1614                        variabilities_noalias.append(var.variability)
1615                        types_noalias.append(var.type)
1616                    else:
1617                        vrefs_alias.append(var.value_reference)
1618                        names_alias.append(var.name)
1619                        aliases_alias.append(var.alias)
1620                        descriptions_alias.append(var.description)
1621                        variabilities_alias.append(var.variability)
1622                        types_alias.append(var.type)
1623                       
1624        # need to save these no alias lists for later
1625        vrefs = vrefs_noalias[:]
1626        names = names_noalias[:]
1627        types = types_noalias[:]
1628        variabilities = variabilities_noalias[:]
1629       
1630        # merge lists
1631        vrefs.extend(vrefs_alias)
1632        names.extend(names_alias)
1633        aliases.extend(aliases_alias)
1634        descriptions.extend(descriptions_alias)
1635        variabilities.extend(variabilities_alias)
1636        types.extend(types_alias)
1637       
1638        # zip to list of tuples and sort - non alias variables are now
1639        # guaranteed to be first in list
1640        names_noalias = sorted(zip(
1641            tuple(vrefs_noalias), 
1642            tuple(names_noalias)), 
1643            key=itemgetter(0))
1644        variabilities_noalias = sorted(zip(
1645            tuple(vrefs_noalias), 
1646            tuple(variabilities_noalias)), 
1647            key=itemgetter(0))
1648        types_noalias = sorted(zip(
1649            tuple(vrefs_noalias), 
1650            tuple(types_noalias)), 
1651            key=itemgetter(0))
1652        names = sorted(zip(
1653            tuple(vrefs), 
1654            tuple(names)), 
1655            key=itemgetter(0))
1656        aliases = sorted(zip(
1657            tuple(vrefs), 
1658            tuple(aliases)), 
1659            key=itemgetter(0))
1660        descriptions = sorted(zip(
1661            tuple(vrefs), 
1662            tuple(descriptions)), 
1663            key=itemgetter(0))
1664        variabilities = sorted(zip(
1665            tuple(vrefs), 
1666            tuple(variabilities)), 
1667            key=itemgetter(0))
1668        types = sorted(zip(
1669            tuple(vrefs), 
1670            tuple(types)), 
1671            key=itemgetter(0))
1672       
1673        num_vars = len(names)
1674       
1675        names_sens = []
1676        descs_sens = []
1677        cont_vars = []
1678       
1679        if parameters != None:
1680           
1681            if isinstance(self.model, fmi.FMUModelME2):
1682                vars = self.model.get_model_variables(type=fmi.FMI2_REAL,include_alias=False,variability=fmi.FMI2_CONTINUOUS,filter=self.options["filter"])
1683                if python3_flag:
1684                    state_vars = [v.value_reference for i,v in self.model.get_states_list().items()]
1685                else:
1686                    state_vars = [v.value_reference for i,v in self.model.get_states_list().iteritems()]
1687            else:
1688                vars = self.model.get_model_variables(type=fmi.FMI_REAL,include_alias=False,variability=fmi.FMI_CONTINUOUS,filter=self.options["filter"])
1689                state_vars = self.model.get_state_value_references()
1690            for i in state_vars:
1691                for j in vars.keys():
1692                    if i == vars[j].value_reference:
1693                        cont_vars.append(vars[j].name)
1694       
1695            for j in range(len(parameters)):
1696                for i in range(len(self.model.continuous_states)):
1697                    names_sens += ['d'+cont_vars[i]+'/d'+parameters[j]]
1698                    descs_sens  += ['Sensitivity of '+cont_vars[i]+' with respect to '+parameters[j]+'.']
1699
1700        # Find the maximum name and description length
1701        max_name_length = len('Time')
1702        max_desc_length = len('Time in [s]')
1703       
1704        for i in range(len(names)):
1705            name = names[i][1]
1706            desc = descriptions[i][1]
1707           
1708            if (len(name)>max_name_length):
1709                max_name_length = len(name)
1710               
1711            if (len(desc)>max_desc_length):
1712                max_desc_length = len(desc)
1713       
1714        for i in range(len(names_sens)):
1715            name = names_sens[i]
1716            desc = descs_sens[i]
1717           
1718            if (len(name)>max_name_length):
1719                max_name_length = len(name)
1720               
1721            if (len(desc)>max_desc_length):
1722                max_desc_length = len(desc)
1723
1724        f.write('char name(%d,%d)\n' % (num_vars+len(names_sens)+1, max_name_length))
1725        f.write('time\n')
1726
1727        for name in names:
1728            f.write(name[1] +'\n')
1729        for name in names_sens:
1730            f.write(name + '\n')
1731
1732        f.write('\n')
1733       
1734        if not opts["result_store_variable_description"]:
1735            max_desc_length = 0
1736            descriptions    = [[0,""] for d in descriptions]
1737            descs_sens      = ["" for d in descs_sens]
1738
1739        # Write descriptions       
1740        f.write('char description(%d,%d)\n' % (num_vars+len(names_sens) + 1, max_desc_length))
1741        f.write('Time in [s]\n')
1742
1743        # Loop over all variables, not only those with a description
1744        for desc in descriptions:
1745            f.write(desc[1] +'\n')
1746        for desc in descs_sens:
1747            f.write(desc + '\n')
1748               
1749        f.write('\n')
1750
1751        # Write data meta information
1752       
1753        f.write('int dataInfo(%d,%d)\n' % (num_vars+len(names_sens) + 1, 4))
1754        f.write('0 1 0 -1 # time\n')
1755
1756        lst_real_cont = dict(zip(self.real_var_ref,range(len(self.real_var_ref))))
1757        lst_int_cont  = dict(zip(self.int_var_ref,[len(self.real_var_ref)+x for x in range(len(self.int_var_ref))]))
1758        lst_bool_cont = dict(zip(self.bool_var_ref,[len(self.real_var_ref)+len(self.int_var_ref)+x for x in range(len(self.bool_var_ref))]))
1759           
1760        valueref_of_continuous_states = []
1761        list_of_parameters = []
1762       
1763        cnt_1 = 1
1764        cnt_2 = 1
1765        n_parameters = 0
1766        datatable1 = False
1767        last_real_vref = -1; last_int_vref = -1; last_bool_vref = -1
1768        for i, name in enumerate(names):
1769            update = False
1770            if (types[i][1] == fmi.FMI_REAL and last_real_vref != name[0]):
1771                last_real_vref = name[0]
1772                update = True
1773            if ((types[i][1] == fmi.FMI_INTEGER or types[i][1] == fmi.FMI_ENUMERATION) and last_int_vref != name[0]):
1774                last_int_vref = name[0]
1775                update = True
1776            if (types[i][1] == fmi.FMI_BOOLEAN and last_bool_vref != name[0]):
1777                last_bool_vref = name[0]
1778                update = True
1779            if update:
1780                if aliases[i][1] == 0:
1781                    if variabilities[i][1] == fmi.FMI_PARAMETER or \
1782                        variabilities[i][1] == fmi.FMI_CONSTANT:
1783                        cnt_1 += 1
1784                        n_parameters += 1
1785                        datatable1 = True
1786                        list_of_parameters.append((types[i][0],types[i][1]))
1787                    else:
1788                        cnt_2 += 1
1789                        #valueref_of_continuous_states.append(
1790                        #    list_of_continuous_states[name[0]])
1791                        if types[i][1] == fmi.FMI_REAL:
1792                            valueref_of_continuous_states.append(lst_real_cont[name[0]])
1793                        elif types[i][1] == fmi.FMI_INTEGER or types[i][1] == fmi.FMI_ENUMERATION:
1794                            valueref_of_continuous_states.append(lst_int_cont[name[0]])
1795                        else:
1796                            valueref_of_continuous_states.append(lst_bool_cont[name[0]])
1797                        datatable1 = False
1798                else:
1799                    base_var = self.model.get_variable_alias_base(name[1])
1800                    variability = self.model.get_variable_variability(base_var)
1801                    data_type = self.model.get_variable_data_type(base_var)
1802                    if data_type != types[i][1]:
1803                        raise Exception
1804                    if variability == fmi.FMI_PARAMETER or \
1805                        variability == fmi.FMI_CONSTANT:
1806                        cnt_1 += 1
1807                        n_parameters += 1
1808                        datatable1 = True
1809                        list_of_parameters.append((types[i][0],types[i][1]))
1810                    else:
1811                        cnt_2 += 1
1812                        #valueref_of_continuous_states.append(
1813                        #    list_of_continuous_states[name[0]])
1814                        if types[i][1] == fmi.FMI_REAL:
1815                            valueref_of_continuous_states.append(lst_real_cont[name[0]])
1816                        elif types[i][1] == fmi.FMI_INTEGER or types[i][1] == fmi.FMI_ENUMERATION:
1817                            valueref_of_continuous_states.append(lst_int_cont[name[0]])
1818                        else:
1819                            valueref_of_continuous_states.append(lst_bool_cont[name[0]])
1820                        datatable1 = False
1821           
1822            if aliases[i][1] == 0: # no alias
1823                #if variabilities[i][1] == fmi.FMI_PARAMETER or \
1824                #    variabilities[i][1] == fmi.FMI_CONSTANT:
1825                if datatable1:
1826                    #cnt_1 += 1
1827                    #n_parameters += 1
1828                    f.write('1 %d 0 -1 # ' % cnt_1 + name[1]+'\n')
1829                    #datatable1 = True
1830                else:
1831                    #cnt_2 += 1
1832                    #valueref_of_continuous_states.append(
1833                    #    list_of_continuous_states[name[0]])
1834                    f.write('2 %d 0 -1 # ' % cnt_2 + name[1] +'\n')
1835                    #datatable1 = False
1836               
1837            elif aliases[i][1] == 1: # alias
1838                if datatable1:
1839                    f.write('1 %d 0 -1 # ' % cnt_1 + name[1]+'\n')
1840                else:
1841                    f.write('2 %d 0 -1 # ' % cnt_2 + name[1] +'\n')
1842            else:
1843                if datatable1:
1844                    f.write('1 -%d 0 -1 # ' % cnt_1 + name[1]+'\n')
1845                else:
1846                    f.write('2 -%d 0 -1 # ' % cnt_2 + name[1] +'\n')
1847        for i, name in enumerate(names_sens):
1848            cnt_2 += 1
1849            f.write('2 %d 0 -1 # ' % cnt_2 + name +'\n')
1850       
1851       
1852        f.write('\n')
1853
1854        # Write data
1855        # Write data set 1
1856        f.write('float data_1(%d,%d)\n' % (2, n_parameters + 1))
1857        f.write("%.14E" % self.model.time)
1858        str_text = ''
1859       
1860        # write constants and parameters
1861        for i, dtype in enumerate(list_of_parameters):
1862            vref = dtype[0]
1863            if dtype[1] == fmi.FMI_REAL:
1864                str_text = str_text + (
1865                    " %.14E" % (self.model.get_real([vref])))
1866            elif dtype[1] == fmi.FMI_INTEGER or dtype[1] == fmi.FMI_ENUMERATION:
1867                str_text = str_text + (
1868                    " %.14E" % (self.model.get_integer([vref])))
1869            elif dtype[1] == fmi.FMI_BOOLEAN:
1870                str_text = str_text + (
1871                    " %.14E" % (float(
1872                        self.model.get_boolean([vref])[0])))
1873                       
1874        f.write(str_text)
1875        f.write('\n')
1876        self._point_last_t = f.tell()
1877        f.write("%s" % ' '*28)
1878        f.write(str_text)
1879
1880        f.write('\n\n')
1881       
1882        self._nvariables = len(valueref_of_continuous_states)+1
1883        self._nvariables_sens = len(names_sens)
1884       
1885       
1886        f.write('float data_2(')
1887        self._point_npoints = f.tell()
1888        f.write(' '*(14+4+14))
1889        f.write('\n')
1890       
1891        #f.write('%s,%d)\n' % (' '*14, self._nvariables))
1892       
1893        self._file = f
1894        self._data_order  = N.array(valueref_of_continuous_states)
1895        self.real_var_ref = N.array(self.real_var_ref)
1896        self.int_var_ref  = N.array(self.int_var_ref)
1897        self.bool_var_ref = N.array(self.bool_var_ref)
1898
1899    def integration_point(self, solver = None):#parameter_data=[]):
1900        """
1901        Writes the current status of the model to file. If the header has not
1902        been written previously it is written now. If data is specified it is
1903        written instead of the current status.
1904       
1905        Parameters::
1906           
1907                data --
1908                    A one dimensional array of variable trajectory data. data
1909                    should consist of information about the status in the order
1910                    specified by FMUModel.save_time_point()
1911                    Default: None
1912        """
1913        f = self._file
1914        data_order = self._data_order
1915        model = self.model
1916
1917        #Retrieves the time-point
1918        r = model.get_real(self.real_var_ref)
1919        i = model.get_integer(self.int_var_ref)
1920        b = model.get_boolean(self.bool_var_ref)
1921       
1922        data = N.append(N.append(r,i),b)
1923
1924        #Write the point
1925        str_text = (" %.14E" % self.model.time) + ''.join([" %.14E" % (data[data_order[j]]) for j in range(self._nvariables-1)])
1926       
1927        #Sets the parameters, if any
1928        if solver and self.options["sensitivities"]:
1929            parameter_data = N.array(solver.interpolate_sensitivity(model.time, 0)).flatten()
1930            for j in range(len(parameter_data)):
1931                str_text = str_text + (" %.14E" % (parameter_data[j]))
1932       
1933        f.write(str_text+'\n')
1934       
1935        #Update number of points
1936        self.nbr_points+=1
1937
1938    def simulation_end(self):
1939        """
1940        Finalize the writing by filling in the blanks in the created file. The
1941        blanks consists of the number of points and the final time (in data set
1942        1). Also closes the file.
1943        """
1944        #If open, finalize and close
1945        if self.file_open:
1946           
1947            f = self._file
1948           
1949            f.seek(self._point_last_t)
1950           
1951            f.write('%.14E'%self.model.time)
1952           
1953            f.seek(self._point_npoints)
1954            f.write('%d,%d)' % (self.nbr_points, self._nvariables+self._nvariables_sens))
1955            #f.write('%d'%self._npoints)
1956            f.seek(-1,2)
1957            #Close the file
1958            f.write('\n')
1959            f.close()
1960            self.file_open = False
1961           
1962    def get_result(self):
1963        """
1964        Method for retrieving the result. This method should return a
1965        result of an instance of ResultBase or of an instance of a
1966        subclass of ResultBase.
1967        """
1968        return ResultDymolaTextual(self.file_name)
1969       
1970    def set_options(self, options):
1971        """
1972        Options are the options dictionary provided to the simulation
1973        method.
1974        """
1975        self.options = options
1976       
1977class ResultHandlerDummy(ResultHandler):
1978    def __init__(self, model):
1979        self.model = model
1980   
1981    def get_result(self):
1982        return None
1983   
1984class JIOError(Exception):
1985    """
1986    Base class for exceptions specific to this module.
1987    """
1988   
1989    def __init__(self, message):
1990        """
1991        Create new error with a specific message.
1992       
1993        Parameters::
1994       
1995            message --
1996                The error message.
1997        """
1998        self.message = message
1999       
2000    def __str__(self):
2001        """
2002        Print error message when class instance is printed.
2003         
2004        Overrides the general-purpose special method such that a string
2005        representation of an instance of this class will be the error message.
2006        """
2007        return self.message
2008
2009
2010class VariableNotFoundError(JIOError):
2011    """
2012    Exception that is thrown when a variable is not found in a data file.
2013    """
2014    pass
2015   
2016class VariableNotTimeVarying(JIOError):
2017    """
2018    Exception that is thrown when a column is asked for a parameter/constant.
2019    """
2020    pass 
2021   
2022def robust_float(value):
2023    """
2024    Function for robust handling of float values such as INF and NAN.
2025    """
2026    try:
2027        return float(value)
2028    except ValueError:
2029        if value.startswith("1.#INF"):
2030            return float(N.inf)
2031        elif value.startswith("-1.#INF"):
2032            return float(-N.inf)
2033        elif value.startswith("1.#QNAN") or value.startswith("-1.#IND"):
2034            return float(N.nan)
2035        else:
2036            raise ValueError
2037
2038mat4_template = {'header': [('type', 'i4'),
2039                            ('mrows', 'i4'),
2040                            ('ncols', 'i4'),
2041                            ('imagf', 'i4'),
2042                            ('namlen', 'i4')]}
2043
2044class ResultHandlerBinaryFile(ResultHandler):
2045    """
2046    Export an optimization or simulation result to file in Dymola's binary result file
2047    format (MATLAB v4 format).
2048    """
2049    def __init__(self, model):
2050        self.model = model
2051   
2052    def _data_header(self, name, nbr_rows, nbr_cols, data_type):
2053        if data_type == "int":
2054            t = 10*2
2055        elif data_type == "double":
2056            t = 10*0
2057        elif data_type == "char":
2058            t = 10*5 + 1
2059        header = np.empty((), mat4_template["header"])
2060        header["type"] = (not SYS_LITTLE_ENDIAN) * 1000 + t
2061        header["mrows"] = nbr_rows
2062        header["ncols"] = nbr_cols
2063        header["imagf"] = 0
2064        header["namlen"] = len(name) + 1
2065       
2066        return header
2067       
2068    def _write_header(self, name, nbr_rows, nbr_cols, data_type):
2069        header = self._data_header(name, nbr_rows, nbr_cols, data_type)
2070       
2071        self._file.write(header.tostring(order="F"))
2072        self._file.write(np.compat.asbytes(name +"\0"))
2073   
2074    def convert_char_array(self, data):
2075        data = np.array(data)
2076        dtype = data.dtype
2077        dims = [data.shape[0], int(data.dtype.str[2:])]
2078       
2079        data = np.ndarray(shape=(dims[0], int(dtype.str[2:])), dtype=dtype.str[:2]+"1", buffer=data)
2080        data[data == ""] = " "
2081       
2082        if dtype.kind == "U": 
2083            tmp = np.ndarray(shape=(), dtype=(dtype.str[:2] + str(np.product(dims))), buffer=data)
2084            buf = tmp.item().encode('latin-1')
2085            data = np.ndarray(shape=dims, dtype="S1", buffer=buf)
2086       
2087        return data
2088       
2089    def dump_data(self, data):
2090        self._file.write(data.tostring(order="F"))
2091       
2092    def dump_native_data(self, data):
2093        self._file.write(data)
2094   
2095    def initialize_complete(self):
2096        pass 
2097   
2098    def simulation_start(self):
2099        """
2100        Opens the file and writes the header. This includes the information
2101        about the variables and a table determining the link between variables
2102        and data.
2103        """
2104        opts = self.options
2105        model = self.model
2106       
2107        #Internal values
2108        self.file_open = False
2109        self.nbr_points = 0
2110       
2111        self.file_name = opts["result_file_name"]
2112        try:
2113            self.parameters = opts["sensitivities"]
2114        except KeyError:
2115            self.parameters = False
2116           
2117        if self.parameters:
2118            raise fmi.FMUException("Storing sensitivity results are not supported using this format. Use the file format instead.")
2119       
2120        if self.file_name == "":
2121            self.file_name=self.model.get_identifier() + '_result.mat'
2122       
2123        file_name = self.file_name
2124        parameters = self.parameters
2125        self._file = open(file_name,'wb')
2126       
2127        aclass_data = ["Atrajectory", "1.1", " ", "binTrans"]
2128        aclass_data = self.convert_char_array(aclass_data)
2129        self._write_header("Aclass", aclass_data.shape[0], aclass_data.shape[1], "char")
2130        self.dump_data(aclass_data)
2131       
2132        # Open file
2133        vars_real = self.model.get_model_variables(type=fmi.FMI_REAL,    filter=self.options["filter"], _as_list=True)#.values()
2134        vars_int  = self.model.get_model_variables(type=fmi.FMI_INTEGER, filter=self.options["filter"], _as_list=True)#.values()
2135        vars_bool = self.model.get_model_variables(type=fmi.FMI_BOOLEAN, filter=self.options["filter"], _as_list=True)#.values()
2136        vars_enum = self.model.get_model_variables(type=fmi.FMI_ENUMERATION, filter=self.options["filter"], _as_list=True)#.values()
2137       
2138        sorted_vars_real = sorted(vars_real, key=attrgetter("value_reference"))
2139        sorted_vars_int  = sorted(vars_int,  key=attrgetter("value_reference"))
2140        sorted_vars_bool = sorted(vars_bool, key=attrgetter("value_reference"))
2141        sorted_vars_enum = sorted(vars_enum, key=attrgetter("value_reference"))
2142       
2143        sorted_vars = sorted_vars_real+sorted_vars_int+sorted_vars_enum+sorted_vars_bool
2144        self._sorted_vars = sorted_vars
2145        len_name_items = len(sorted_vars)+1
2146        len_desc_items = len_name_items
2147       
2148        len_name_data, name_data, len_desc_data, desc_data = fmi_util.convert_sorted_vars_name_desc(sorted_vars)
2149       
2150        self._write_header("name", len_name_data, len_name_items, "char")
2151        self.dump_native_data(name_data)
2152       
2153        if not opts["result_store_variable_description"]:
2154            len_desc_data = 1
2155            desc_data = encode(" "*len_desc_items)
2156       
2157        self._write_header("description", len_desc_data, len_desc_items, "char")
2158        self.dump_native_data(desc_data)
2159       
2160        #Create the data info structure (and return parameters)
2161        data_info = np.zeros((4, len_name_items), dtype=np.int32)
2162        [parameter_data, sorted_vars_real_vref, sorted_vars_int_vref, sorted_vars_bool_vref]  = fmi_util.prepare_data_info(data_info, sorted_vars, self.model)
2163       
2164        self._write_header("dataInfo", data_info.shape[0], data_info.shape[1], "int")
2165        self.dump_data(data_info)
2166
2167        #Dump parameters to file
2168        self._write_header("data_1", len(parameter_data), 2, "double")
2169        self.dump_data(parameter_data)
2170       
2171        #Record the position so that we can later modify the end time
2172        self.data_1_header_position = self._file.tell()
2173        self.dump_data(parameter_data)
2174       
2175        #Record the position so that we can later modify the number of result points stored
2176        self.data_2_header_position = self._file.tell()
2177        self._write_header("data_2", len(sorted_vars_real_vref)+len(sorted_vars_int_vref)+len(sorted_vars_bool_vref)+1, 1, "double")
2178       
2179        self.real_var_ref = np.array(sorted_vars_real_vref)
2180        self.int_var_ref  = np.array(sorted_vars_int_vref)
2181        self.bool_var_ref = np.array(sorted_vars_bool_vref)
2182        self.nbr_points = 0
2183
2184    def integration_point(self, solver = None):
2185        """
2186        Writes the current status of the model to file. If the header has not
2187        been written previously it is written now. If data is specified it is
2188        written instead of the current status.
2189       
2190        Parameters::
2191           
2192                data --
2193                    A one dimensional array of variable trajectory data. data
2194                    should consist of information about the status in the order
2195                    specified by FMUModel.save_time_point()
2196                    Default: None
2197        """
2198        f = self._file
2199        model = self.model
2200
2201        #Retrieves the time-point
2202        r = model.get_real(self.real_var_ref)
2203        i = model.get_integer(self.int_var_ref).astype(float)
2204        b = model.get_boolean(self.bool_var_ref).astype(float)
2205       
2206        #self._write_data(data)
2207        self.dump_data(np.array(float(model.time)))
2208        self.dump_data(r)
2209        self.dump_data(i)
2210        self.dump_data(b)
2211       
2212        #Increment number of points
2213        self.nbr_points += 1
2214
2215    def simulation_end(self):
2216        """
2217        Finalize the writing by filling in the blanks in the created file. The
2218        blanks consists of the number of points and the final time (in data set
2219        1). Also closes the file.
2220        """
2221        #If open, finalize and close
2222        f = self._file
2223       
2224        if f:
2225            f.seek(self.data_1_header_position)
2226            t = np.array([float(self.model.time)])
2227            self.dump_data(t)
2228           
2229            f.seek(self.data_2_header_position)
2230            self._write_header("data_2", len(self.real_var_ref)+len(self.int_var_ref)+len(self.bool_var_ref)+1, self.nbr_points, "double")
2231           
2232            f.close()
2233            self._file = None
2234           
2235    def get_result(self):
2236        """
2237        Method for retrieving the result. This method should return a
2238        result of an instance of ResultBase or of an instance of a
2239        subclass of ResultBase.
2240        """
2241        return ResultDymolaBinary(self.file_name)
2242       
2243    def set_options(self, options):
2244        """
2245        Options are the options dictionary provided to the simulation
2246        method.
2247        """
2248        self.options = options
Note: See TracBrowser for help on using the repository browser.