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