1# -*- coding: utf-8 -*-
2# This file is part of QuTiP: Quantum Toolbox in Python.
3#
4#    Copyright (c) 2016 and later, Alexander J G Pitchford
5#    All rights reserved.
6#
7#    Redistribution and use in source and binary forms, with or without
8#    modification, are permitted provided that the following conditions are
9#    met:
10#
11#    1. Redistributions of source code must retain the above copyright notice,
12#       this list of conditions and the following disclaimer.
13#
14#    2. Redistributions in binary form must reproduce the above copyright
15#       notice, this list of conditions and the following disclaimer in the
16#       documentation and/or other materials provided with the distribution.
17#
18#    3. Neither the name of the QuTiP: Quantum Toolbox in Python nor the names
19#       of its contributors may be used to endorse or promote products derived
20#       from this software without specific prior written permission.
21#
22#    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
23#    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
24#    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
25#    PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
26#    HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
27#    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
28#    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
29#    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
30#    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
31#    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
32#    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
33###############################################################################
34
35"""
36Classes that enable the storing of historical objects created during the
37pulse optimisation.
38These are intented for debugging.
39See the optimizer and dynamics objects for instrutcions on how to enable
40data dumping.
41"""
42
43import os
44import numpy as np
45import copy
46# QuTiP logging
47import qutip.logging_utils
48logger = qutip.logging_utils.get_logger()
49# QuTiP control modules
50import qutip.control.io as qtrlio
51from numpy.compat import asbytes
52
53DUMP_DIR = "~/.qtrl_dump"
54
55def _is_string(var):
56    try:
57        if isinstance(var, basestring):
58            return True
59    except NameError:
60        try:
61            if isinstance(var, str):
62                return True
63        except:
64            return False
65    except:
66        return False
67
68    return False
69
70class Dump(object):
71    """
72    A container for dump items.
73    The lists for dump items is depends on the type
74    Note: abstract class
75
76    Attributes
77    ----------
78    parent : some control object (Dynamics or Optimizer)
79        aka the host. Object that generates the data that is dumped and is
80        host to this dump object.
81
82    dump_dir : str
83        directory where files (if any) will be written out
84        the path and be relative or absolute
85        use ~/ to specify user home directory
86        Note: files are only written when write_to_file is True
87        of writeout is called explicitly
88        Defaults to ~/.qtrl_dump
89
90    level : string
91        level of data dumping: SUMMARY, FULL or CUSTOM
92        See property docstring for details
93        Set automatically if dump is created by the setting host dumping attrib
94
95    write_to_file : bool
96        When set True data and summaries (as configured) will be written
97        interactively to file during the processing
98        Set during instantiation by the host based on its dump_to_file attrib
99
100    dump_file_ext : str
101        Default file extension for any file names that are auto generated
102
103    fname_base : str
104        First part of any auto generated file names.
105        This is usually overridden in the subclass
106
107    dump_summary : bool
108        If True a summary is recorded each time a new item is added to the
109        the dump.
110        Default is True
111
112    summary_sep : str
113        delimiter for the summary file.
114        default is a space
115
116    data_sep : str
117        delimiter for the data files (arrays saved to file).
118        default is a space
119
120    summary_file : str
121        File path for summary file.
122        Automatically generated. Can be set specifically
123
124    """
125    def __init__(self):
126        self.reset()
127
128    def reset(self):
129        if self.parent:
130            self.log_level = self.parent.log_level
131            self.write_to_file = self.parent.dump_to_file
132        else:
133            self.write_to_file = False
134        self._dump_dir = None
135        self.dump_file_ext = "txt"
136        self._fname_base = 'dump'
137        self.dump_summary = True
138        self.summary_sep = ' '
139        self.data_sep = ' '
140        self._summary_file_path = None
141        self._summary_file_specified = False
142
143    @property
144    def log_level(self):
145        return logger.level
146
147    @log_level.setter
148    def log_level(self, lvl):
149        """
150        Set the log_level attribute and set the level of the logger
151        that is call logger.setLevel(lvl)
152        """
153        logger.setLevel(lvl)
154
155    @property
156    def level(self):
157        """
158        The level of data dumping that will occur.
159
160        SUMMARY
161            A summary will be recorded
162
163        FULL
164            All possible dumping
165
166        CUSTOM
167            Some customised level of dumping
168
169        When first set to CUSTOM this is equivalent to SUMMARY. It is then up
170        to the user to specify what specifically is dumped
171        """
172        lvl = 'CUSTOM'
173        if (self.dump_summary and not self.dump_any):
174            lvl = 'SUMMARY'
175        elif (self.dump_summary and self.dump_all):
176            lvl = 'FULL'
177
178        return lvl
179
180    @level.setter
181    def level(self, value):
182        self._level = value
183        self._apply_level()
184
185    @property
186    def dump_any(self):
187        raise NotImplemented("This is an abstract class, "
188                    "use subclass such as DynamicsDump or OptimDump")
189
190    @property
191    def dump_all(self):
192        raise NotImplemented("This is an abstract class, "
193                    "use subclass such as DynamicsDump or OptimDump")
194
195    @property
196    def dump_dir(self):
197        if self._dump_dir is None:
198            self.create_dump_dir()
199        return self._dump_dir
200
201    @dump_dir.setter
202    def dump_dir(self, value):
203        self._dump_dir = value
204        if not self.create_dump_dir():
205            self._dump_dir = None
206
207    def create_dump_dir(self):
208        """
209        Checks dump directory exists, creates it if not
210        """
211        if self._dump_dir is None or len(self._dump_dir) == 0:
212            self._dump_dir = DUMP_DIR
213
214        dir_ok, self._dump_dir, msg = qtrlio.create_dir(
215                    self._dump_dir, desc='dump')
216
217        if not dir_ok:
218            self.write_to_file = False
219            msg += "\ndump file output will be suppressed."
220            logger.error(msg)
221
222        return dir_ok
223
224    @property
225    def fname_base(self):
226        return self._fname_base
227
228    @fname_base.setter
229    def fname_base(self, value):
230        if not _is_string(value):
231            raise ValueError("File name base must be a string")
232        self._fname_base = value
233        self._summary_file_path = None
234
235    @property
236    def summary_file(self):
237        if self._summary_file_path is None:
238            fname = "{}-summary.{}".format(self._fname_base, self.dump_file_ext)
239            self._summary_file_path = os.path.join(self.dump_dir, fname)
240        return self._summary_file_path
241
242    @summary_file.setter
243    def summary_file(self, value):
244        if not _is_string(value):
245            raise ValueError("File path must be a string")
246        self._summary_file_specified = True
247        if os.path.abspath(value):
248            self._summary_file_path = value
249        elif '~' in value:
250            self._summary_file_path = os.path.expanduser(value)
251        else:
252            self._summary_file_path = os.path.join(self.dump_dir, value)
253
254class OptimDump(Dump):
255    """
256    A container for dumps of optimisation data generated during the pulse
257    optimisation.
258
259    Attributes
260    ----------
261    dump_summary : bool
262        When True summary items are appended to the iter_summary
263
264    iter_summary : list of :class:`optimizer.OptimIterSummary`
265        Summary at each iteration
266
267    dump_fid_err : bool
268        When True values are appended to the fid_err_log
269
270    fid_err_log : list of float
271        Fidelity error at each call of the fid_err_func
272
273    dump_grad_norm : bool
274        When True values are appended to the fid_err_log
275
276    grad_norm_log : list of float
277        Gradient norm at each call of the grad_norm_log
278
279    dump_grad : bool
280        When True values are appended to the grad_log
281
282    grad_log : list of ndarray
283        Gradients at each call of the fid_grad_func
284
285    """
286    def __init__(self, optim, level='SUMMARY'):
287        from qutip.control.optimizer import Optimizer
288        if not isinstance(optim, Optimizer):
289            raise TypeError("Must instantiate with {} type".format(
290                                        Optimizer))
291        self.parent = optim
292        self._level = level
293        self.reset()
294
295    def reset(self):
296        Dump.reset(self)
297        self._apply_level()
298        self.iter_summary = []
299        self.fid_err_log = []
300        self.grad_norm_log = []
301        self.grad_log = []
302        self._fname_base = 'optimdump'
303        self._fid_err_file = None
304        self._grad_norm_file = None
305
306    def clear(self):
307        del self.iter_summary[:]
308        self.fid_err_log[:]
309        self.grad_norm_log[:]
310        self.grad_log[:]
311
312    @property
313    def dump_any(self):
314        """True if anything other than the summary is to be dumped"""
315        if (self.dump_fid_err or self.dump_grad_norm or self.dump_grad):
316            return True
317        else:
318            return False
319
320    @property
321    def dump_all(self):
322        """True if everything (ignoring the summary) is to be dumped"""
323        if (self.dump_fid_err and self.dump_grad_norm and self.dump_grad):
324            return True
325        else:
326            return False
327
328    def _apply_level(self, level=None):
329        if level is None:
330            level = self._level
331
332        if not _is_string(level):
333            raise ValueError("Dump level must be a string")
334        level = level.upper()
335        if level == 'CUSTOM':
336            if self._level == 'CUSTOM':
337                # dumping level has not changed keep the same specific config
338                pass
339            else:
340                # Switching to custom, start from SUMMARY
341                level = 'SUMMARY'
342
343        if level == 'SUMMARY':
344            self.dump_summary = True
345            self.dump_fid_err = False
346            self.dump_grad_norm = False
347            self.dump_grad = False
348        elif level == 'FULL':
349            self.dump_summary = True
350            self.dump_fid_err = True
351            self.dump_grad_norm = True
352            self.dump_grad = True
353        else:
354            raise ValueError("No option for dumping level '{}'".format(level))
355
356    def add_iter_summary(self):
357        """add copy of current optimizer iteration summary"""
358        optim = self.parent
359        if optim.iter_summary is None:
360            raise RuntimeError("Cannot add iter_summary as not available")
361        ois = copy.copy(optim.iter_summary)
362        ois.idx = len(self.iter_summary)
363        self.iter_summary.append(ois)
364        if self.write_to_file:
365            if ois.idx == 0:
366                f = open(self.summary_file, 'w')
367                f.write("{}\n{}\n".format(
368                            ois.get_header_line(self.summary_sep),
369                            ois.get_value_line(self.summary_sep)))
370            else:
371                f = open(self.summary_file, 'a')
372                f.write("{}\n".format(
373                            ois.get_value_line(self.summary_sep)))
374
375            f.close()
376        return ois
377
378    @property
379    def fid_err_file(self):
380        if self._fid_err_file is None:
381            fname = "{}-fid_err_log.{}".format(self.fname_base,
382                                                    self.dump_file_ext)
383            self._fid_err_file = os.path.join(self.dump_dir, fname)
384        return self._fid_err_file
385
386    def update_fid_err_log(self, fid_err):
387        """add an entry to the fid_err log"""
388        self.fid_err_log.append(fid_err)
389        if self.write_to_file:
390            if len(self.fid_err_log) == 1:
391                mode = 'w'
392            else:
393                mode = 'a'
394            f = open(self.fid_err_file, mode)
395            f.write("{}\n".format(fid_err))
396            f.close()
397
398    @property
399    def grad_norm_file(self):
400        if self._grad_norm_file is None:
401            fname = "{}-grad_norm_log.{}".format(self.fname_base,
402                                                    self.dump_file_ext)
403            self._grad_norm_file = os.path.join(self.dump_dir, fname)
404        return self._grad_norm_file
405
406    def update_grad_norm_log(self, grad_norm):
407        """add an entry to the grad_norm log"""
408        self.grad_norm_log.append(grad_norm)
409        if self.write_to_file:
410            if len(self.grad_norm_log) == 1:
411                mode = 'w'
412            else:
413                mode = 'a'
414            f = open(self.grad_norm_file, mode)
415            f.write("{}\n".format(grad_norm))
416            f.close()
417
418    def update_grad_log(self, grad):
419        """add an entry to the grad log"""
420        self.grad_log.append(grad)
421        if self.write_to_file:
422            fname = "{}-fid_err_gradients{}.{}".format(self.fname_base,
423                                                        len(self.grad_log),
424                                                        self.dump_file_ext)
425            fpath = os.path.join(self.dump_dir, fname)
426            np.savetxt(fpath, grad, delimiter=self.data_sep)
427
428    def writeout(self, f=None):
429        """write all the logs and the summary out to file(s)
430
431        Parameters
432        ----------
433        f : filename or filehandle
434            If specified then all summary and  object data will go in one file.
435            If None is specified then type specific files will be generated
436            in the dump_dir
437            If a filehandle is specified then it must be a byte mode file
438            as numpy.savetxt is used, and requires this.
439        """
440        fall = None
441        # If specific file given then write everything to it
442        if hasattr(f, 'write'):
443            if not 'b' in f.mode:
444                raise RuntimeError("File stream must be in binary mode")
445            # write all to this stream
446            fall = f
447            fs = f
448            closefall = False
449            closefs = False
450        elif f:
451            # Assume f is a filename
452            fall = open(f, 'wb')
453            fs = fall
454            closefs = False
455            closefall = True
456        else:
457            self.create_dump_dir()
458            closefall = False
459            if self.dump_summary:
460                fs = open(self.summary_file, 'wb')
461                closefs = True
462
463        if self.dump_summary:
464            for ois in self.iter_summary:
465                if ois.idx == 0:
466                    fs.write(asbytes("{}\n{}\n".format(
467                                ois.get_header_line(self.summary_sep),
468                                ois.get_value_line(self.summary_sep))))
469                else:
470                    fs.write(asbytes("{}\n".format(
471                                ois.get_value_line(self.summary_sep))))
472
473            if closefs:
474                fs.close()
475                logger.info("Optim dump summary saved to {}".format(
476                                                    self.summary_file))
477
478        if self.dump_fid_err:
479            if fall:
480                fall.write(asbytes("Fidelity errors:\n"))
481                np.savetxt(fall, self.fid_err_log)
482            else:
483                np.savetxt(self.fid_err_file, self.fid_err_log)
484
485        if self.dump_grad_norm:
486            if fall:
487                fall.write(asbytes("gradients norms:\n"))
488                np.savetxt(fall, self.grad_norm_log)
489            else:
490                np.savetxt(self.grad_norm_file, self.grad_norm_log)
491
492        if self.dump_grad:
493            g_num = 0
494            for grad in self.grad_log:
495                g_num += 1
496                if fall:
497                    fall.write(asbytes("gradients (call {}):\n".format(g_num)))
498                    np.savetxt(fall, grad)
499                else:
500                    fname = "{}-fid_err_gradients{}.{}".format(self.fname_base,
501                                                            g_num,
502                                                            self.dump_file_ext)
503                    fpath = os.path.join(self.dump_dir, fname)
504                    np.savetxt(fpath, grad, delimiter=self.data_sep)
505
506        if closefall:
507            fall.close()
508            logger.info("Optim dump saved to {}".format(f))
509        else:
510            if fall:
511                logger.info("Optim dump saved to specified stream")
512            else:
513                logger.info("Optim dump saved to {}".format(self.dump_dir))
514
515class DynamicsDump(Dump):
516    """
517    A container for dumps of dynamics data. Mainly time evolution calculations.
518
519    Attributes
520    ----------
521    dump_summary : bool
522        If True a summary is recorded
523
524    evo_summary : list of :class:`tslotcomp.EvoCompSummary`
525        Summary items are appended if dump_summary is True
526        at each recomputation of the evolution.
527
528    dump_amps : bool
529        If True control amplitudes are dumped
530
531    dump_dyn_gen : bool
532        If True the dynamics generators (Hamiltonians) are dumped
533
534    dump_prop : bool
535        If True propagators are dumped
536
537    dump_prop_grad : bool
538        If True propagator gradients are dumped
539
540    dump_fwd_evo : bool
541        If True forward evolution operators are dumped
542
543    dump_onwd_evo : bool
544        If True onward evolution operators are dumped
545
546    dump_onto_evo : bool
547        If True onto (or backward) evolution operators are dumped
548
549    evo_dumps : list of :class:`EvoCompDumpItem`
550        A new dump item is appended at each recomputation of the evolution.
551        That is if any of the calculation objects are to be dumped.
552
553    """
554    def __init__(self, dynamics, level='SUMMARY'):
555        from qutip.control.dynamics import Dynamics
556        if not isinstance(dynamics, Dynamics):
557            raise TypeError("Must instantiate with {} type".format(
558                                        Dynamics))
559        self.parent = dynamics
560        self._level = level
561        self.reset()
562
563    def reset(self):
564        Dump.reset(self)
565        self._apply_level()
566        self.evo_dumps = []
567        self.evo_summary = []
568        self._fname_base = 'dyndump'
569
570    def clear(self):
571        del self.evo_dumps[:]
572        del self.evo_summary[:]
573
574    @property
575    def dump_any(self):
576        """True if any of the calculation objects are to be dumped"""
577        if (self.dump_amps or
578                self.dump_dyn_gen or
579                self.dump_prop or
580                self.dump_prop_grad or
581                self.dump_fwd_evo or
582                self.dump_onwd_evo or
583                self.dump_onto_evo):
584            return True
585        else:
586            return False
587
588    @property
589    def dump_all(self):
590        """True if all of the calculation objects are to be dumped"""
591        dyn = self.parent
592        if (self.dump_amps and
593                    self.dump_dyn_gen and
594                    self.dump_prop and
595                    self.dump_prop_grad and
596                    self.dump_fwd_evo and
597                    (self.dump_onwd_evo) or
598                    (self.dump_onwd_evo == dyn.fid_computer.uses_onwd_evo) and
599                    (self.dump_onto_evo or
600                    (self.dump_onto_evo == dyn.fid_computer.uses_onto_evo))):
601            return True
602        else:
603            return False
604
605    def _apply_level(self, level=None):
606        dyn = self.parent
607        if level is None:
608            level = self._level
609
610        if not _is_string(level):
611            raise ValueError("Dump level must be a string")
612        level = level.upper()
613        if level == 'CUSTOM':
614            if self._level == 'CUSTOM':
615                # dumping level has not changed keep the same specific config
616                pass
617            else:
618                # Switching to custom, start from SUMMARY
619                level = 'SUMMARY'
620
621        if level == 'SUMMARY':
622            self.dump_summary = True
623            self.dump_amps = False
624            self.dump_dyn_gen = False
625            self.dump_prop = False
626            self.dump_prop_grad = False
627            self.dump_fwd_evo = False
628            self.dump_onwd_evo = False
629            self.dump_onto_evo = False
630        elif level == 'FULL':
631            self.dump_summary = True
632            self.dump_amps = True
633            self.dump_dyn_gen = True
634            self.dump_prop = True
635            self.dump_prop_grad = True
636            self.dump_fwd_evo = True
637            self.dump_onwd_evo = dyn.fid_computer.uses_onwd_evo
638            self.dump_onto_evo = dyn.fid_computer.uses_onto_evo
639        else:
640            raise ValueError("No option for dumping level '{}'".format(level))
641
642    def add_evo_dump(self):
643        """Add dump of current time evolution generating objects"""
644        dyn = self.parent
645        item = EvoCompDumpItem(self)
646        item.idx = len(self.evo_dumps)
647        self.evo_dumps.append(item)
648        if self.dump_amps:
649            item.ctrl_amps = copy.deepcopy(dyn.ctrl_amps)
650        if self.dump_dyn_gen:
651            item.dyn_gen = copy.deepcopy(dyn._dyn_gen)
652        if self.dump_prop:
653            item.prop = copy.deepcopy(dyn._prop)
654        if self.dump_prop_grad:
655            item.prop_grad = copy.deepcopy(dyn._prop_grad)
656        if self.dump_fwd_evo:
657            item.fwd_evo = copy.deepcopy(dyn._fwd_evo)
658        if self.dump_onwd_evo:
659            item.onwd_evo = copy.deepcopy(dyn._onwd_evo)
660        if self.dump_onto_evo:
661            item.onto_evo = copy.deepcopy(dyn._onto_evo)
662
663        if self.write_to_file:
664            item.writeout()
665        return item
666
667    def add_evo_comp_summary(self, dump_item_idx=None):
668        """add copy of current evo comp summary"""
669        dyn = self.parent
670        if dyn.tslot_computer.evo_comp_summary is None:
671            raise RuntimeError("Cannot add evo_comp_summary as not available")
672        ecs = copy.copy(dyn.tslot_computer.evo_comp_summary)
673        ecs.idx = len(self.evo_summary)
674        ecs.evo_dump_idx = dump_item_idx
675        if dyn.stats:
676            ecs.iter_num = dyn.stats.num_iter
677            ecs.fid_func_call_num = dyn.stats.num_fidelity_func_calls
678            ecs.grad_func_call_num = dyn.stats.num_grad_func_calls
679
680        self.evo_summary.append(ecs)
681        if self.write_to_file:
682            if ecs.idx == 0:
683                f = open(self.summary_file, 'w')
684                f.write("{}\n{}\n".format(
685                        ecs.get_header_line(self.summary_sep),
686                        ecs.get_value_line(self.summary_sep)))
687            else:
688                f = open(self.summary_file, 'a')
689                f.write("{}\n".format(ecs.get_value_line(self.summary_sep)))
690
691            f.close()
692        return ecs
693
694    def writeout(self, f=None):
695        """
696        Write all the dump items and the summary out to file(s).
697
698        Parameters
699        ----------
700        f : filename or filehandle
701            If specified then all summary and object data will go in one file.
702            If None is specified then type specific files will be generated in
703            the dump_dir.  If a filehandle is specified then it must be a byte
704            mode file as numpy.savetxt is used, and requires this.
705        """
706        fall = None
707        # If specific file given then write everything to it
708        if hasattr(f, 'write'):
709            if not 'b' in f.mode:
710                raise RuntimeError("File stream must be in binary mode")
711            # write all to this stream
712            fall = f
713            fs = f
714            closefall = False
715            closefs = False
716        elif f:
717            # Assume f is a filename
718            fall = open(f, 'wb')
719            fs = fall
720            closefs = False
721            closefall = True
722        else:
723            self.create_dump_dir()
724            closefall = False
725            if self.dump_summary:
726                fs = open(self.summary_file, 'wb')
727                closefs = True
728
729        if self.dump_summary:
730            for ecs in self.evo_summary:
731                if ecs.idx == 0:
732                    fs.write(asbytes("{}\n{}\n".format(
733                            ecs.get_header_line(self.summary_sep),
734                            ecs.get_value_line(self.summary_sep))))
735                else:
736                    fs.write(asbytes("{}\n".format(
737                            ecs.get_value_line(self.summary_sep))))
738
739            if closefs:
740                fs.close()
741                logger.info("Dynamics dump summary saved to {}".format(
742                                                    self.summary_file))
743
744        for di in self.evo_dumps:
745            di.writeout(fall)
746
747        if closefall:
748            fall.close()
749            logger.info("Dynamics dump saved to {}".format(f))
750        else:
751            if fall:
752                logger.info("Dynamics dump saved to specified stream")
753            else:
754                logger.info("Dynamics dump saved to {}".format(self.dump_dir))
755
756
757class DumpItem:
758    """
759    An item in a dump list
760    """
761    def __init__(self):
762        pass
763
764
765class EvoCompDumpItem(DumpItem):
766    """
767    A copy of all objects generated to calculate one time evolution. Note the
768    attributes are only set if the corresponding :class:`DynamicsDump`
769    ``dump_*`` attribute is set.
770    """
771    def __init__(self, dump):
772        if not isinstance(dump, DynamicsDump):
773            raise TypeError("Must instantiate with {} type".format(
774                                        DynamicsDump))
775        self.parent = dump
776        self.reset()
777
778    def reset(self):
779        self.idx = None
780#        self.num_ctrls = None
781#        self.num_tslots = None
782        self.ctrl_amps = None
783        self.dyn_gen = None
784        self.prop = None
785        self.prop_grad = None
786        self.fwd_evo = None
787        self.onwd_evo = None
788        self.onto_evo = None
789
790    def writeout(self, f=None):
791        """ write all the objects out to files
792
793        Parameters
794        ----------
795        f : filename or filehandle
796            If specified then all object data will go in one file.
797            If None is specified then type specific files will be generated
798            in the dump_dir
799            If a filehandle is specified then it must be a byte mode file
800            as numpy.savetxt is used, and requires this.
801        """
802        dump = self.parent
803        fall = None
804        closefall = True
805        closef = False
806        # If specific file given then write everything to it
807        if hasattr(f, 'write'):
808            if not 'b' in f.mode:
809                raise RuntimeError("File stream must be in binary mode")
810            # write all to this stream
811            fall = f
812            closefall = False
813            f.write(asbytes("EVOLUTION COMPUTATION {}\n".format(self.idx)))
814        elif f:
815            fall = open(f, 'wb')
816        else:
817            # otherwise files for each type will be created
818            fnbase = "{}-evo{}".format(dump._fname_base, self.idx)
819            closefall = False
820
821        #ctrl amps
822        if not self.ctrl_amps is None:
823            if fall:
824                f = fall
825                f.write(asbytes("Ctrl amps\n"))
826            else:
827                fname = "{}-ctrl_amps.{}".format(fnbase,
828                                                dump.dump_file_ext)
829                f = open(os.path.join(dump.dump_dir, fname), 'wb')
830                closef = True
831            np.savetxt(f, self.ctrl_amps, fmt='%14.6g',
832                       delimiter=dump.data_sep)
833            if closef: f.close()
834
835        # dynamics generators
836        if not self.dyn_gen is None:
837            k = 0
838            if fall:
839                f = fall
840                f.write(asbytes("Dynamics Generators\n"))
841            else:
842                fname = "{}-dyn_gen.{}".format(fnbase,
843                                                dump.dump_file_ext)
844                f = open(os.path.join(dump.dump_dir, fname), 'wb')
845                closef = True
846            for dg in self.dyn_gen:
847                f.write(asbytes(
848                        "dynamics generator for timeslot {}\n".format(k)))
849                np.savetxt(f, self.dyn_gen[k], delimiter=dump.data_sep)
850                k += 1
851            if closef: f.close()
852
853        # Propagators
854        if not self.prop is None:
855            k = 0
856            if fall:
857                f = fall
858                f.write(asbytes("Propagators\n"))
859            else:
860                fname = "{}-prop.{}".format(fnbase,
861                                                dump.dump_file_ext)
862                f = open(os.path.join(dump.dump_dir, fname), 'wb')
863                closef = True
864            for dg in self.dyn_gen:
865                f.write(asbytes("Propagator for timeslot {}\n".format(k)))
866                np.savetxt(f, self.prop[k], delimiter=dump.data_sep)
867                k += 1
868            if closef: f.close()
869
870        # Propagator gradient
871        if not self.prop_grad is None:
872            k = 0
873            if fall:
874                f = fall
875                f.write(asbytes("Propagator gradients\n"))
876            else:
877                fname = "{}-prop_grad.{}".format(fnbase,
878                                                dump.dump_file_ext)
879                f = open(os.path.join(dump.dump_dir, fname), 'wb')
880                closef = True
881            for k in range(self.prop_grad.shape[0]):
882                for j in range(self.prop_grad.shape[1]):
883                    f.write(asbytes("Propagator gradient for timeslot {} "
884                            "control {}\n".format(k, j)))
885                    np.savetxt(f, self.prop_grad[k, j],
886                               delimiter=dump.data_sep)
887            if closef: f.close()
888
889        # forward evolution
890        if not self.fwd_evo is None:
891            k = 0
892            if fall:
893                f = fall
894                f.write(asbytes("Forward evolution\n"))
895            else:
896                fname = "{}-fwd_evo.{}".format(fnbase,
897                                                dump.dump_file_ext)
898                f = open(os.path.join(dump.dump_dir, fname), 'wb')
899                closef = True
900            for dg in self.dyn_gen:
901                f.write(asbytes("Evolution from 0 to {}\n".format(k)))
902                np.savetxt(f, self.fwd_evo[k], delimiter=dump.data_sep)
903                k += 1
904            if closef: f.close()
905
906        # onward evolution
907        if not self.onwd_evo is None:
908            k = 0
909            if fall:
910                f = fall
911                f.write(asbytes("Onward evolution\n"))
912            else:
913                fname = "{}-onwd_evo.{}".format(fnbase,
914                                                dump.dump_file_ext)
915                f = open(os.path.join(dump.dump_dir, fname), 'wb')
916                closef = True
917            for dg in self.dyn_gen:
918                f.write(asbytes("Evolution from {} to end\n".format(k)))
919                np.savetxt(f, self.fwd_evo[k], delimiter=dump.data_sep)
920                k += 1
921            if closef: f.close()
922
923        # onto evolution
924        if not self.onto_evo is None:
925            k = 0
926            if fall:
927                f = fall
928                f.write(asbytes("Onto evolution\n"))
929            else:
930                fname = "{}-onto_evo.{}".format(fnbase,
931                                                dump.dump_file_ext)
932                f = open(os.path.join(dump.dump_dir, fname), 'wb')
933                closef = True
934            for dg in self.dyn_gen:
935                f.write(asbytes("Evolution from {} onto target\n".format(k)))
936                np.savetxt(f, self.fwd_evo[k], delimiter=dump.data_sep)
937                k += 1
938            if closef: f.close()
939
940        if closefall:
941            fall.close()
942
943class DumpSummaryItem:
944    """
945    A summary of the most recent iteration.  Abstract class only.
946
947    Attributes
948    ----------
949    idx : int
950        Index in the summary list in which this is stored
951    """
952    min_col_width = 11
953    summary_property_names = ()
954
955    summary_property_fmt_type = ()
956
957    summary_property_fmt_prec = ()
958
959    @classmethod
960    def get_header_line(cls, sep=' '):
961        if sep == ' ':
962            line = ''
963            i = 0
964            for a in cls.summary_property_names:
965                if i > 0:
966                    line += sep
967                i += 1
968                line += format(a, str(max(len(a), cls.min_col_width)) + 's')
969        else:
970            line = sep.join(cls.summary_property_names)
971        return line
972
973    def reset(self):
974        self.idx = 0
975
976    def get_value_line(self, sep=' '):
977        line = ""
978        i = 0
979        for a in zip(self.summary_property_names,
980                     self.summary_property_fmt_type,
981                     self.summary_property_fmt_prec):
982            if i > 0:
983                line += sep
984            i += 1
985            v = getattr(self, a[0])
986            w = max(len(a[0]), self.min_col_width)
987            if v is not None:
988                fmt = ''
989                if sep == ' ':
990                    fmt += str(w)
991                else:
992                    fmt += '0'
993                if a[2] > 0:
994                    fmt += '.' + str(a[2])
995                fmt += a[1]
996                line += format(v, fmt)
997            else:
998                if sep == ' ':
999                    line += format('None', str(w) + 's')
1000                else:
1001                    line += 'None'
1002
1003        return line
1004