pyerrors.input.dobs
1import datetime 2import getpass 3import gzip 4import json 5import socket 6import warnings 7from collections import defaultdict 8 9import lxml.etree as et 10import numpy as np 11 12from .. import version as pyerrorsversion 13from ..covobs import Covobs 14from ..obs import Obs, _merge_idx 15 16 17# Based on https://stackoverflow.com/a/10076823 18def _etree_to_dict(t): 19 """ Convert the content of an XML file to a python dict""" 20 d = {t.tag: {} if t.attrib else None} 21 children = list(t) 22 if children: 23 dd = defaultdict(list) 24 for dc in map(_etree_to_dict, children): 25 for k, v in dc.items(): 26 dd[k].append(v) 27 d = {t.tag: {k: v[0] if len(v) == 1 else v 28 for k, v in dd.items()}} 29 if t.attrib: 30 d[t.tag].update(('@' + k, v) 31 for k, v in t.attrib.items()) 32 if t.text: 33 text = t.text.strip() 34 if children or t.attrib: 35 if text: 36 d[t.tag]['#data'] = [text] 37 else: 38 d[t.tag] = text 39 return d 40 41 42def _dict_to_xmlstring(d): 43 if isinstance(d, dict): 44 iters = '' 45 for k in d: 46 if k.startswith('#'): 47 for la in d[k]: 48 iters += la 49 iters = '<array>\n' + iters + '<{}array>\n'.format('/') 50 return iters 51 if isinstance(d[k], dict): 52 iters += f'<{k}>\n' + _dict_to_xmlstring(d[k]) + '<{}{}>\n'.format('/', k) 53 elif isinstance(d[k], str): 54 if len(d[k]) > 100: 55 iters += f'<{k}>\n ' + d[k] + ' \n<{}{}>\n'.format('/', k) 56 else: 57 iters += f'<{k}> ' + d[k] + ' <{}{}>\n'.format('/', k) 58 elif isinstance(d[k], list): 59 for i in range(len(d[k])): 60 iters += _dict_to_xmlstring(d[k][i]) 61 elif not d[k]: 62 return '\n' 63 else: 64 raise TypeError(f'Type {type(d[k]).__name__} not supported in export!') 65 else: 66 raise TypeError(f'Type {type(d).__name__} not supported in export!') 67 return iters 68 69 70def _dict_to_xmlstring_spaces(d, space=' '): 71 s = _dict_to_xmlstring(d) 72 o = '' 73 c = 0 74 cm = False 75 for li in s.split('\n'): 76 if li.startswith('<{}'.format('/')): 77 c -= 1 78 cm = True 79 for _i in range(c): 80 o += space 81 o += li + '\n' 82 if li.startswith('<') and not cm: 83 if '<{}'.format('/') not in li: 84 c += 1 85 cm = False 86 return o 87 88 89def create_pobs_string(obsl, name, spec='', origin='', symbol=None, enstag=None): 90 """Export a list of Obs or structures containing Obs to an xml string 91 according to the Zeuthen pobs format. 92 93 Tags are not written or recovered automatically. The separator | is removed from the replica names. 94 95 Parameters 96 ---------- 97 obsl : list 98 List of Obs that will be exported. 99 The Obs inside a structure have to be defined on the same ensemble. 100 name : str 101 The name of the observable. 102 spec : str 103 Optional string that describes the contents of the file. 104 origin : str 105 Specify where the data has its origin. 106 symbol : list 107 A list of symbols that describe the observables to be written. May be empty. 108 enstag : str 109 Enstag that is written to pobs. If None, the ensemble name is used. 110 111 Returns 112 ------- 113 xml_str : str 114 XML formatted string of the input data 115 """ 116 117 if symbol is None: 118 symbol = [] 119 120 od = {} 121 ename = obsl[0].e_names[0] 122 names = list(obsl[0].deltas.keys()) 123 nr = len(names) 124 onames = [name.replace('|', '') for name in names] 125 for o in obsl: 126 if len(o.e_names) != 1: 127 raise ValueError('You try to export dobs to obs!') 128 if o.e_names[0] != ename: 129 raise ValueError('You try to export dobs to obs!') 130 if len(o.deltas.keys()) != nr: 131 raise ValueError('Incompatible obses in list') 132 od['observables'] = {} 133 od['observables']['schema'] = {'name': 'lattobs', 'version': '1.0'} 134 od['observables']['origin'] = { 135 'who': getpass.getuser(), 136 'date': str(datetime.datetime.now())[:-7], 137 'host': socket.gethostname(), 138 'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}} 139 od['observables']['pobs'] = {} 140 pd = od['observables']['pobs'] 141 pd['spec'] = spec 142 pd['origin'] = origin 143 pd['name'] = name 144 if enstag: 145 if not isinstance(enstag, str): 146 raise TypeError('enstag has to be a string!') 147 pd['enstag'] = enstag 148 else: 149 pd['enstag'] = ename 150 pd['nr'] = f'{nr}' 151 pd['array'] = [] 152 osymbol = 'cfg' 153 if not isinstance(symbol, list): 154 raise TypeError('Symbol has to be a list!') 155 if not (len(symbol) == 0 or len(symbol) == len(obsl)): 156 raise ValueError(f'Symbol has to be a list of length 0 or {len(obsl)}!') 157 for s in symbol: 158 osymbol += f' {s}' 159 for r in range(nr): 160 ad = {} 161 ad['id'] = onames[r] 162 Nconf = len(obsl[0].deltas[names[r]]) 163 layout = f'{Nconf} i f{len(obsl)}' 164 ad['layout'] = layout 165 ad['symbol'] = osymbol 166 data = '' 167 for c in range(Nconf): 168 data += f'{obsl[0].idl[names[r]][c]} ' 169 for o in obsl: 170 num = o.deltas[names[r]][c] + o.r_values[names[r]] 171 if num == 0: 172 data += '0 ' 173 else: 174 data += f'{num:1.16e} ' 175 data += '\n' 176 ad['#data'] = data 177 pd['array'].append(ad) 178 179 rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dict_to_xmlstring_spaces(od) 180 return rs 181 182 183def write_pobs(obsl, fname, name, spec='', origin='', symbol=None, enstag=None, gz=True): 184 """Export a list of Obs or structures containing Obs to a .xml.gz file 185 according to the Zeuthen pobs format. 186 187 Tags are not written or recovered automatically. The separator | is removed from the replica names. 188 189 Parameters 190 ---------- 191 obsl : list 192 List of Obs that will be exported. 193 The Obs inside a structure have to be defined on the same ensemble. 194 fname : str 195 Filename of the output file. 196 name : str 197 The name of the observable. 198 spec : str 199 Optional string that describes the contents of the file. 200 origin : str 201 Specify where the data has its origin. 202 symbol : list 203 A list of symbols that describe the observables to be written. May be empty. 204 enstag : str 205 Enstag that is written to pobs. If None, the ensemble name is used. 206 gz : bool 207 If True, the output is a gzipped xml. If False, the output is an xml file. 208 209 Returns 210 ------- 211 None 212 """ 213 pobsstring = create_pobs_string(obsl, name, spec, origin, symbol, enstag) 214 215 if not fname.endswith('.xml') and not fname.endswith('.gz'): 216 fname += '.xml' 217 218 if gz: 219 if not fname.endswith('.gz'): 220 fname += '.gz' 221 222 fp = gzip.open(fname, 'wb') 223 fp.write(pobsstring.encode('utf-8')) 224 else: 225 fp = open(fname, 'w', encoding='utf-8') 226 fp.write(pobsstring) 227 fp.close() 228 229 230def _import_data(string): 231 return json.loads("[" + ",".join(string.replace(' +', ' ').split()) + "]") 232 233 234def _check(condition): 235 if not condition: 236 raise Exception("XML file format not supported") 237 238 239class _NoTagInDataError(Exception): 240 """Raised when tag is not in data""" 241 def __init__(self, tag): 242 self.tag = tag 243 super().__init__(f'Tag {self.tag} not in data!') 244 245 246def _find_tag(dat, tag): 247 for i in range(len(dat)): 248 if dat[i].tag == tag: 249 return i 250 raise _NoTagInDataError(tag) 251 252 253def _import_array(arr): 254 name = arr[_find_tag(arr, 'id')].text.strip() 255 index = _find_tag(arr, 'layout') 256 try: 257 sindex = _find_tag(arr, 'symbol') 258 except _NoTagInDataError: 259 sindex = 0 260 if sindex > index: 261 tmp = _import_data(arr[sindex].tail) 262 else: 263 tmp = _import_data(arr[index].tail) 264 265 li = arr[index].text.strip() 266 m = li.split() 267 if m[1] == "i" and m[2][0] == "f": 268 nc = int(m[0]) 269 na = int(m[2].lstrip('f')) 270 _dat = [] 271 mask = [] 272 for a in range(na): 273 mask += [a] 274 _dat += [np.array(tmp[1 + a:: na + 1])] 275 _check(len(tmp[0:: na + 1]) == nc) 276 return [name, tmp[0:: na + 1], mask, _dat] 277 elif m[1][0] == 'f' and len(m) < 3: 278 sh = (int(m[0]), int(m[1].lstrip('f'))) 279 return np.reshape(tmp, sh) 280 elif any(['f' in s for s in m]): 281 for si in range(len(m)): 282 if m[si] == 'f': 283 break 284 sh = [int(m[i]) for i in range(si)] 285 return np.reshape(tmp, sh) 286 else: 287 print(name, m) 288 _check(False) 289 290 291def _import_rdata(rd): 292 name, idx, _mask, deltas = _import_array(rd) 293 return deltas, name, idx 294 295 296def _import_cdata(cd): 297 _check(cd[0].tag == "id") 298 _check(cd[1][0].text.strip() == "cov") 299 cov = _import_array(cd[1]) 300 grad = _import_array(cd[2]) 301 return cd[0].text.strip(), cov, grad 302 303 304def read_pobs(fname, full_output=False, gz=True, separator_insertion=None): 305 """Import a list of Obs from an xml.gz file in the Zeuthen pobs format. 306 307 Tags are not written or recovered automatically. 308 309 Parameters 310 ---------- 311 fname : str 312 Filename of the input file. 313 full_output : bool 314 If True, a dict containing auxiliary information and the data is returned. 315 If False, only the data is returned as list. 316 separatior_insertion: str or int 317 str: replace all occurences of "separator_insertion" within the replica names 318 by "|%s" % (separator_insertion) when constructing the names of the replica. 319 int: Insert the separator "|" at the position given by separator_insertion. 320 None (default): Replica names remain unchanged. 321 322 Returns 323 ------- 324 res : list[Obs] 325 Imported data 326 or 327 res : dict 328 Imported data and meta-data 329 """ 330 331 if not fname.endswith('.xml') and not fname.endswith('.gz'): 332 fname += '.xml' 333 if gz: 334 if not fname.endswith('.gz'): 335 fname += '.gz' 336 with gzip.open(fname, 'r') as fin: 337 content = fin.read() 338 else: 339 if fname.endswith('.gz'): 340 warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2) 341 with open(fname) as fin: 342 content = fin.read() 343 344 # parse xml file content 345 root = et.fromstring(content) 346 347 _check(root[2].tag == 'pobs') 348 pobs = root[2] 349 350 version = root[0][1].text.strip() 351 352 _check(root[1].tag == 'origin') 353 file_origin = _etree_to_dict(root[1])['origin'] 354 355 deltas = [] 356 names = [] 357 idl = [] 358 for i in range(5, len(pobs)): 359 delta, name, idx = _import_rdata(pobs[i]) 360 deltas.append(delta) 361 if separator_insertion is None: 362 pass 363 elif isinstance(separator_insertion, int): 364 name = name[:separator_insertion] + '|' + name[separator_insertion:] 365 elif isinstance(separator_insertion, str): 366 name = name.replace(separator_insertion, f"|{separator_insertion}") 367 else: 368 raise TypeError(f"separator_insertion has to be string or int, is {type(separator_insertion).__name__}") 369 names.append(name) 370 idl.append(idx) 371 res = [Obs([d[i] for d in deltas], names, idl=idl) for i in range(len(deltas[0]))] 372 373 descriptiond = {} 374 for i in range(4): 375 descriptiond[pobs[i].tag] = pobs[i].text.strip() 376 377 _check(pobs[4].tag == "nr") 378 379 _check(pobs[5].tag == 'array') 380 if pobs[5][1].tag == 'symbol': 381 symbol = pobs[5][1].text.strip() 382 descriptiond['symbol'] = symbol 383 384 if full_output: 385 retd = {} 386 tool = file_origin.get('tool', None) 387 if tool: 388 program = tool['name'] + ' ' + tool['version'] 389 else: 390 program = '' 391 retd['program'] = program 392 retd['version'] = version 393 retd['who'] = file_origin['who'] 394 retd['date'] = file_origin['date'] 395 retd['host'] = file_origin['host'] 396 retd['description'] = descriptiond 397 retd['obsdata'] = res 398 return retd 399 else: 400 return res 401 402 403# this is based on Mattia Bruno's implementation at https://github.com/mbruno46/pyobs/blob/master/pyobs/IO/xml.py 404def import_dobs_string(content, full_output=False, separator_insertion=True): 405 """Import a list of Obs from a string in the Zeuthen dobs format. 406 407 Tags are not written or recovered automatically. 408 409 Parameters 410 ---------- 411 content : str 412 XML string containing the data 413 full_output : bool 414 If True, a dict containing auxiliary information and the data is returned. 415 If False, only the data is returned as list. 416 separatior_insertion: str, int or bool 417 str: replace all occurences of "separator_insertion" within the replica names 418 by "|%s" % (separator_insertion) when constructing the names of the replica. 419 int: Insert the separator "|" at the position given by separator_insertion. 420 True (default): separator "|" is inserted after len(ensname), assuming that the 421 ensemble name is a prefix to the replica name. 422 None or False: No separator is inserted. 423 424 Returns 425 ------- 426 res : list[Obs] 427 Imported data 428 or 429 res : dict 430 Imported data and meta-data 431 """ 432 433 root = et.fromstring(content) 434 435 _check(root.tag == 'OBSERVABLES') 436 _check(root[0].tag == 'SCHEMA') 437 version = root[0][1].text.strip() 438 439 _check(root[1].tag == 'origin') 440 file_origin = _etree_to_dict(root[1])['origin'] 441 442 _check(root[2].tag == 'dobs') 443 444 dobs = root[2] 445 446 descriptiond = {} 447 for i in range(3): 448 descriptiond[dobs[i].tag] = dobs[i].text.strip() 449 450 _check(dobs[3].tag == 'array') 451 452 symbol = [] 453 if dobs[3][1].tag == 'symbol': 454 symbol = dobs[3][1].text.strip() 455 descriptiond['symbol'] = symbol 456 mean = _import_array(dobs[3])[0] 457 458 _check(dobs[4].tag == "ne") 459 ne = int(dobs[4].text.strip()) 460 _check(dobs[5].tag == "nc") 461 462 idld = {} 463 deltad = {} 464 covd = {} 465 gradd = {} 466 names = [] 467 e_names = [] 468 enstags = {} 469 for k in range(6, len(list(dobs))): 470 if dobs[k].tag == "edata": 471 _check(dobs[k][0].tag == "enstag") 472 ename = dobs[k][0].text.strip() 473 e_names.append(ename) 474 _check(dobs[k][1].tag == "nr") 475 R = int(dobs[k][1].text.strip()) 476 for i in range(2, 2 + R): 477 deltas, rname, idx = _import_rdata(dobs[k][i]) 478 if separator_insertion is None or False: 479 pass 480 elif separator_insertion is True: 481 if rname.startswith(ename): 482 rname = rname[:len(ename)] + '|' + rname[len(ename):] 483 elif isinstance(separator_insertion, int): 484 rname = rname[:separator_insertion] + '|' + rname[separator_insertion:] 485 elif isinstance(separator_insertion, str): 486 rname = rname.replace(separator_insertion, f"|{separator_insertion}") 487 else: 488 raise TypeError(f"separator_insertion has to be string or int, is {type(separator_insertion).__name__}") 489 if '|' in rname: 490 new_ename = rname[:rname.index('|')] 491 else: 492 new_ename = ename 493 enstags[new_ename] = ename 494 idld[rname] = idx 495 deltad[rname] = deltas 496 names.append(rname) 497 elif dobs[k].tag == "cdata": 498 cname, cov, grad = _import_cdata(dobs[k]) 499 covd[cname] = cov 500 if grad.shape[1] == 1: 501 gradd[cname] = [grad for i in range(len(mean))] 502 else: 503 gradd[cname] = grad.T 504 else: 505 _check(False) 506 names = list(set(names)) 507 508 for name in names: 509 for i in range(len(deltad[name])): 510 tmp = np.zeros_like(deltad[name][i]) 511 for j in range(len(deltad[name][i])): 512 if deltad[name][i][j] != 0.: 513 tmp[j] = deltad[name][i][j] + mean[i] 514 deltad[name][i] = tmp 515 516 res = [] 517 for i in range(len(mean)): 518 deltas = [] 519 idl = [] 520 obs_names = [] 521 for name in names: 522 h = np.unique(deltad[name][i]) 523 if len(h) == 1 and np.all(h == mean[i]): 524 continue 525 repdeltas = [] 526 repidl = [] 527 for j in range(len(deltad[name][i])): 528 if deltad[name][i][j] != 0.: 529 repdeltas.append(deltad[name][i][j]) 530 repidl.append(idld[name][j]) 531 if len(repdeltas) > 0: 532 obs_names.append(name) 533 deltas.append(repdeltas) 534 idl.append(repidl) 535 536 obsmeans = [np.average(deltas[j]) for j in range(len(deltas))] 537 res.append(Obs([np.array(deltas[j]) - obsmeans[j] for j in range(len(obsmeans))], obs_names, idl=idl, means=obsmeans)) 538 res[-1]._value = mean[i] 539 _check(len(e_names) == ne) 540 541 cnames = list(covd.keys()) 542 for i in range(len(res)): 543 new_covobs = {name: Covobs(0, covd[name], name, grad=gradd[name][i]) for name in cnames} 544 for name in cnames: 545 if np.all(new_covobs[name].grad == 0): 546 del new_covobs[name] 547 cnames_loc = list(new_covobs.keys()) 548 for name in cnames_loc: 549 res[i].names.append(name) 550 res[i].shape[name] = 1 551 res[i].idl[name] = [] 552 res[i]._covobs = new_covobs 553 554 if symbol: 555 for i in range(len(res)): 556 res[i].tag = symbol[i] 557 if res[i].tag == 'None': 558 res[i].tag = None 559 if full_output: 560 retd = {} 561 tool = file_origin.get('tool', None) 562 if tool: 563 program = tool['name'] + ' ' + tool['version'] 564 else: 565 program = '' 566 retd['program'] = program 567 retd['version'] = version 568 retd['who'] = file_origin['who'] 569 retd['date'] = file_origin['date'] 570 retd['host'] = file_origin['host'] 571 retd['description'] = descriptiond 572 retd['enstags'] = enstags 573 retd['obsdata'] = res 574 return retd 575 else: 576 return res 577 578 579def read_dobs(fname, full_output=False, gz=True, separator_insertion=True): 580 """Import a list of Obs from an xml.gz file in the Zeuthen dobs format. 581 582 Tags are not written or recovered automatically. 583 584 Parameters 585 ---------- 586 fname : str 587 Filename of the input file. 588 full_output : bool 589 If True, a dict containing auxiliary information and the data is returned. 590 If False, only the data is returned as list. 591 gz : bool 592 If True, assumes that data is gzipped. If False, assumes XML file. 593 separatior_insertion: str, int or bool 594 str: replace all occurences of "separator_insertion" within the replica names 595 by "|%s" % (separator_insertion) when constructing the names of the replica. 596 int: Insert the separator "|" at the position given by separator_insertion. 597 True (default): separator "|" is inserted after len(ensname), assuming that the 598 ensemble name is a prefix to the replica name. 599 None or False: No separator is inserted. 600 601 Returns 602 ------- 603 res : list[Obs] 604 Imported data 605 or 606 res : dict 607 Imported data and meta-data 608 """ 609 610 if not fname.endswith('.xml') and not fname.endswith('.gz'): 611 fname += '.xml' 612 if gz: 613 if not fname.endswith('.gz'): 614 fname += '.gz' 615 with gzip.open(fname, 'r') as fin: 616 content = fin.read() 617 else: 618 if fname.endswith('.gz'): 619 warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2) 620 with open(fname) as fin: 621 content = fin.read() 622 623 return import_dobs_string(content, full_output, separator_insertion=separator_insertion) 624 625 626def _dobsdict_to_xmlstring(d): 627 if isinstance(d, dict): 628 iters = '' 629 for k in d: 630 if k.startswith('#value'): 631 for li in d[k]: 632 iters += li 633 return iters + '\n' 634 elif k.startswith('#'): 635 for li in d[k]: 636 iters += li 637 iters = '<array>\n' + iters + '<{}array>\n'.format('/') 638 return iters 639 if isinstance(d[k], dict): 640 iters += f'<{k}>\n' + _dobsdict_to_xmlstring(d[k]) + '<{}{}>\n'.format('/', k) 641 elif isinstance(d[k], str): 642 if len(d[k]) > 100: 643 iters += f'<{k}>\n ' + d[k] + ' \n<{}{}>\n'.format('/', k) 644 else: 645 iters += f'<{k}> ' + d[k] + ' <{}{}>\n'.format('/', k) 646 elif isinstance(d[k], list): 647 tmps = '' 648 if k in ['edata', 'cdata']: 649 for i in range(len(d[k])): 650 tmps += f'<{k}>\n' + _dobsdict_to_xmlstring(d[k][i]) + f'</{k}>\n' 651 else: 652 for i in range(len(d[k])): 653 tmps += _dobsdict_to_xmlstring(d[k][i]) 654 iters += tmps 655 elif isinstance(d[k], (int, float)): 656 iters += f'<{k}> ' + str(d[k]) + ' <{}{}>\n'.format('/', k) 657 elif not d[k]: 658 return '\n' 659 else: 660 raise TypeError(f'Type {type(d[k]).__name__} not supported in export!') 661 else: 662 raise TypeError(f'Type {type(d).__name__} not supported in export!') 663 return iters 664 665 666def _dobsdict_to_xmlstring_spaces(d, space=' '): 667 s = _dobsdict_to_xmlstring(d) 668 o = '' 669 c = 0 670 cm = False 671 for li in s.split('\n'): 672 if li.startswith('<{}'.format('/')): 673 c -= 1 674 cm = True 675 for _i in range(c): 676 o += space 677 o += li + '\n' 678 if li.startswith('<') and not cm: 679 if '<{}'.format('/') not in li: 680 c += 1 681 cm = False 682 return o 683 684 685def create_dobs_string(obsl, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None): 686 """Generate the string for the export of a list of Obs or structures containing Obs 687 to a .xml.gz file according to the Zeuthen dobs format. 688 689 Tags are not written or recovered automatically. The separator |is removed from the replica names. 690 691 Parameters 692 ---------- 693 obsl : list 694 List of Obs that will be exported. 695 The Obs inside a structure do not have to be defined on the same set of configurations, 696 but the storage requirement is increased, if this is not the case. 697 name : str 698 The name of the observable. 699 spec : str 700 Optional string that describes the contents of the file. 701 origin : str 702 Specify where the data has its origin. 703 symbol : list 704 A list of symbols that describe the observables to be written. May be empty. 705 who : str 706 Provide the name of the person that exports the data. 707 enstags : dict 708 Provide alternative enstag for ensembles in the form enstags = {ename: enstag} 709 Otherwise, the ensemble name is used. 710 711 Returns 712 ------- 713 xml_str : str 714 XML string generated from the data 715 """ 716 if enstags is None: 717 enstags = {} 718 if symbol is None: 719 symbol = [] 720 od = {} 721 r_names = [] 722 for o in obsl: 723 r_names += [name for name in o.names if name.split('|')[0] in o.mc_names] 724 r_names = sorted(set(r_names)) 725 mc_names = sorted(set([n.split('|')[0] for n in r_names])) 726 for tmpname in mc_names: 727 if tmpname not in enstags: 728 enstags[tmpname] = tmpname 729 ne = len(set(mc_names)) 730 cov_names = [] 731 for o in obsl: 732 cov_names += list(o.cov_names) 733 cov_names = sorted(set(cov_names)) 734 nc = len(set(cov_names)) 735 od['OBSERVABLES'] = {} 736 od['OBSERVABLES']['SCHEMA'] = {'NAME': 'lattobs', 'VERSION': '1.0'} 737 if who is None: 738 who = getpass.getuser() 739 od['OBSERVABLES']['origin'] = { 740 'who': who, 741 'date': str(datetime.datetime.now())[:-7], 742 'host': socket.gethostname(), 743 'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}} 744 od['OBSERVABLES']['dobs'] = {} 745 pd = od['OBSERVABLES']['dobs'] 746 pd['spec'] = spec 747 pd['origin'] = origin 748 pd['name'] = name 749 pd['array'] = {} 750 pd['array']['id'] = 'val' 751 pd['array']['layout'] = f'1 f{len(obsl)}' 752 osymbol = '' 753 if symbol: 754 if not isinstance(symbol, list): 755 raise TypeError('Symbol has to be a list!') 756 if not (len(symbol) == 0 or len(symbol) == len(obsl)): 757 raise ValueError(f'Symbol has to be a list of length 0 or {len(obsl)}!') 758 osymbol = symbol[0] 759 for s in symbol[1:]: 760 osymbol += f' {s}' 761 pd['array']['symbol'] = osymbol 762 763 pd['array']['#values'] = [' '.join([f'{o.value:1.16e}' for o in obsl])] 764 pd['ne'] = f'{ne}' 765 pd['nc'] = f'{nc}' 766 pd['edata'] = [] 767 for name in mc_names: 768 ed = {} 769 ed['enstag'] = enstags[name] 770 onames = sorted([n for n in r_names if (n.startswith(name + '|') or n == name)]) 771 nr = len(onames) 772 ed['nr'] = nr 773 ed[''] = [] 774 775 for r in range(nr): 776 ad = {} 777 repname = onames[r] 778 ad['id'] = repname.replace('|', '') 779 idx = _merge_idx([o.idl.get(repname, []) for o in obsl]) 780 Nconf = len(idx) 781 layout = f'{Nconf} i f{len(obsl)}' 782 ad['layout'] = layout 783 data = '' 784 counters = [0 for o in obsl] 785 offsets = [o.r_values[repname] - o.value if repname in o.r_values else 0 for o in obsl] 786 for ci in idx: 787 data += f'{ci} ' 788 for oi in range(len(obsl)): 789 o = obsl[oi] 790 if repname in o.idl: 791 if counters[oi] < 0: 792 num = 0 793 if num == 0: 794 data += '0 ' 795 else: 796 data += f'{num:1.16e} ' 797 continue 798 if o.idl[repname][counters[oi]] == ci: 799 num = o.deltas[repname][counters[oi]] + offsets[oi] 800 if num == 0: 801 data += '0 ' 802 else: 803 data += f'{num:1.16e} ' 804 counters[oi] += 1 805 if counters[oi] >= len(o.idl[repname]): 806 counters[oi] = -1 807 else: 808 num = 0 809 if num == 0: 810 data += '0 ' 811 else: 812 data += f'{num:1.16e} ' 813 else: 814 data += '0 ' 815 data += '\n' 816 ad['#data'] = data 817 ed[''].append(ad) 818 pd['edata'].append(ed) 819 820 allcov = {} 821 for o in obsl: 822 for cname in o.cov_names: 823 if cname in allcov: 824 if not np.array_equal(allcov[cname], o.covobs[cname].cov): 825 raise Exception(f'Inconsistent covariance matrices for {cname}!') 826 else: 827 allcov[cname] = o.covobs[cname].cov 828 pd['cdata'] = [] 829 for cname in cov_names: 830 cd = {} 831 cd['id'] = cname 832 833 covd = {'id': 'cov'} 834 if allcov[cname].shape == (): 835 ncov = 1 836 covd['layout'] = '1 1 f' 837 covd['#data'] = f'{allcov[cname]:1.14e}' 838 else: 839 shape = allcov[cname].shape 840 assert (shape[0] == shape[1]) 841 ncov = shape[0] 842 covd['layout'] = f'{ncov} {ncov} f' 843 ds = '' 844 for i in range(ncov): 845 for j in range(ncov): 846 val = allcov[cname][i][j] 847 if val == 0: 848 ds += '0 ' 849 else: 850 ds += f'{val:1.14e} ' 851 ds += '\n' 852 covd['#data'] = ds 853 854 gradd = {'id': 'grad'} 855 gradd['layout'] = f'{ncov} f{len(obsl)}' 856 ds = '' 857 for i in range(ncov): 858 for o in obsl: 859 if cname in o.covobs: 860 val = o.covobs[cname].grad[i].item() 861 if val != 0: 862 ds += f'{val:1.14e} ' 863 else: 864 ds += '0 ' 865 else: 866 ds += '0 ' 867 gradd['#data'] = ds 868 cd['array'] = [covd, gradd] 869 pd['cdata'].append(cd) 870 871 rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dobsdict_to_xmlstring_spaces(od) 872 873 return rs 874 875 876def write_dobs(obsl, fname, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None, gz=True): 877 """Export a list of Obs or structures containing Obs to a .xml.gz file 878 according to the Zeuthen dobs format. 879 880 Tags are not written or recovered automatically. The separator | is removed from the replica names. 881 882 Parameters 883 ---------- 884 obsl : list 885 List of Obs that will be exported. 886 The Obs inside a structure do not have to be defined on the same set of configurations, 887 but the storage requirement is increased, if this is not the case. 888 fname : str 889 Filename of the output file. 890 name : str 891 The name of the observable. 892 spec : str 893 Optional string that describes the contents of the file. 894 origin : str 895 Specify where the data has its origin. 896 symbol : list 897 A list of symbols that describe the observables to be written. May be empty. 898 who : str 899 Provide the name of the person that exports the data. 900 enstags : dict 901 Provide alternative enstag for ensembles in the form enstags = {ename: enstag} 902 Otherwise, the ensemble name is used. 903 gz : bool 904 If True, the output is a gzipped XML. If False, the output is a XML file. 905 906 Returns 907 ------- 908 None 909 """ 910 if enstags is None: 911 enstags = {} 912 913 dobsstring = create_dobs_string(obsl, name, spec, origin, symbol, who, enstags=enstags) 914 915 if not fname.endswith('.xml') and not fname.endswith('.gz'): 916 fname += '.xml' 917 918 if gz: 919 if not fname.endswith('.gz'): 920 fname += '.gz' 921 922 fp = gzip.open(fname, 'wb') 923 fp.write(dobsstring.encode('utf-8')) 924 else: 925 fp = open(fname, 'w', encoding='utf-8') 926 fp.write(dobsstring) 927 fp.close()
90def create_pobs_string(obsl, name, spec='', origin='', symbol=None, enstag=None): 91 """Export a list of Obs or structures containing Obs to an xml string 92 according to the Zeuthen pobs format. 93 94 Tags are not written or recovered automatically. The separator | is removed from the replica names. 95 96 Parameters 97 ---------- 98 obsl : list 99 List of Obs that will be exported. 100 The Obs inside a structure have to be defined on the same ensemble. 101 name : str 102 The name of the observable. 103 spec : str 104 Optional string that describes the contents of the file. 105 origin : str 106 Specify where the data has its origin. 107 symbol : list 108 A list of symbols that describe the observables to be written. May be empty. 109 enstag : str 110 Enstag that is written to pobs. If None, the ensemble name is used. 111 112 Returns 113 ------- 114 xml_str : str 115 XML formatted string of the input data 116 """ 117 118 if symbol is None: 119 symbol = [] 120 121 od = {} 122 ename = obsl[0].e_names[0] 123 names = list(obsl[0].deltas.keys()) 124 nr = len(names) 125 onames = [name.replace('|', '') for name in names] 126 for o in obsl: 127 if len(o.e_names) != 1: 128 raise ValueError('You try to export dobs to obs!') 129 if o.e_names[0] != ename: 130 raise ValueError('You try to export dobs to obs!') 131 if len(o.deltas.keys()) != nr: 132 raise ValueError('Incompatible obses in list') 133 od['observables'] = {} 134 od['observables']['schema'] = {'name': 'lattobs', 'version': '1.0'} 135 od['observables']['origin'] = { 136 'who': getpass.getuser(), 137 'date': str(datetime.datetime.now())[:-7], 138 'host': socket.gethostname(), 139 'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}} 140 od['observables']['pobs'] = {} 141 pd = od['observables']['pobs'] 142 pd['spec'] = spec 143 pd['origin'] = origin 144 pd['name'] = name 145 if enstag: 146 if not isinstance(enstag, str): 147 raise TypeError('enstag has to be a string!') 148 pd['enstag'] = enstag 149 else: 150 pd['enstag'] = ename 151 pd['nr'] = f'{nr}' 152 pd['array'] = [] 153 osymbol = 'cfg' 154 if not isinstance(symbol, list): 155 raise TypeError('Symbol has to be a list!') 156 if not (len(symbol) == 0 or len(symbol) == len(obsl)): 157 raise ValueError(f'Symbol has to be a list of length 0 or {len(obsl)}!') 158 for s in symbol: 159 osymbol += f' {s}' 160 for r in range(nr): 161 ad = {} 162 ad['id'] = onames[r] 163 Nconf = len(obsl[0].deltas[names[r]]) 164 layout = f'{Nconf} i f{len(obsl)}' 165 ad['layout'] = layout 166 ad['symbol'] = osymbol 167 data = '' 168 for c in range(Nconf): 169 data += f'{obsl[0].idl[names[r]][c]} ' 170 for o in obsl: 171 num = o.deltas[names[r]][c] + o.r_values[names[r]] 172 if num == 0: 173 data += '0 ' 174 else: 175 data += f'{num:1.16e} ' 176 data += '\n' 177 ad['#data'] = data 178 pd['array'].append(ad) 179 180 rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dict_to_xmlstring_spaces(od) 181 return rs
Export a list of Obs or structures containing Obs to an xml string according to the Zeuthen pobs format.
Tags are not written or recovered automatically. The separator | is removed from the replica names.
Parameters
- obsl (list): List of Obs that will be exported. The Obs inside a structure have to be defined on the same ensemble.
- name (str): The name of the observable.
- spec (str): Optional string that describes the contents of the file.
- origin (str): Specify where the data has its origin.
- symbol (list): A list of symbols that describe the observables to be written. May be empty.
- enstag (str): Enstag that is written to pobs. If None, the ensemble name is used.
Returns
- xml_str (str): XML formatted string of the input data
184def write_pobs(obsl, fname, name, spec='', origin='', symbol=None, enstag=None, gz=True): 185 """Export a list of Obs or structures containing Obs to a .xml.gz file 186 according to the Zeuthen pobs format. 187 188 Tags are not written or recovered automatically. The separator | is removed from the replica names. 189 190 Parameters 191 ---------- 192 obsl : list 193 List of Obs that will be exported. 194 The Obs inside a structure have to be defined on the same ensemble. 195 fname : str 196 Filename of the output file. 197 name : str 198 The name of the observable. 199 spec : str 200 Optional string that describes the contents of the file. 201 origin : str 202 Specify where the data has its origin. 203 symbol : list 204 A list of symbols that describe the observables to be written. May be empty. 205 enstag : str 206 Enstag that is written to pobs. If None, the ensemble name is used. 207 gz : bool 208 If True, the output is a gzipped xml. If False, the output is an xml file. 209 210 Returns 211 ------- 212 None 213 """ 214 pobsstring = create_pobs_string(obsl, name, spec, origin, symbol, enstag) 215 216 if not fname.endswith('.xml') and not fname.endswith('.gz'): 217 fname += '.xml' 218 219 if gz: 220 if not fname.endswith('.gz'): 221 fname += '.gz' 222 223 fp = gzip.open(fname, 'wb') 224 fp.write(pobsstring.encode('utf-8')) 225 else: 226 fp = open(fname, 'w', encoding='utf-8') 227 fp.write(pobsstring) 228 fp.close()
Export a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen pobs format.
Tags are not written or recovered automatically. The separator | is removed from the replica names.
Parameters
- obsl (list): List of Obs that will be exported. The Obs inside a structure have to be defined on the same ensemble.
- fname (str): Filename of the output file.
- name (str): The name of the observable.
- spec (str): Optional string that describes the contents of the file.
- origin (str): Specify where the data has its origin.
- symbol (list): A list of symbols that describe the observables to be written. May be empty.
- enstag (str): Enstag that is written to pobs. If None, the ensemble name is used.
- gz (bool): If True, the output is a gzipped xml. If False, the output is an xml file.
Returns
- None
305def read_pobs(fname, full_output=False, gz=True, separator_insertion=None): 306 """Import a list of Obs from an xml.gz file in the Zeuthen pobs format. 307 308 Tags are not written or recovered automatically. 309 310 Parameters 311 ---------- 312 fname : str 313 Filename of the input file. 314 full_output : bool 315 If True, a dict containing auxiliary information and the data is returned. 316 If False, only the data is returned as list. 317 separatior_insertion: str or int 318 str: replace all occurences of "separator_insertion" within the replica names 319 by "|%s" % (separator_insertion) when constructing the names of the replica. 320 int: Insert the separator "|" at the position given by separator_insertion. 321 None (default): Replica names remain unchanged. 322 323 Returns 324 ------- 325 res : list[Obs] 326 Imported data 327 or 328 res : dict 329 Imported data and meta-data 330 """ 331 332 if not fname.endswith('.xml') and not fname.endswith('.gz'): 333 fname += '.xml' 334 if gz: 335 if not fname.endswith('.gz'): 336 fname += '.gz' 337 with gzip.open(fname, 'r') as fin: 338 content = fin.read() 339 else: 340 if fname.endswith('.gz'): 341 warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2) 342 with open(fname) as fin: 343 content = fin.read() 344 345 # parse xml file content 346 root = et.fromstring(content) 347 348 _check(root[2].tag == 'pobs') 349 pobs = root[2] 350 351 version = root[0][1].text.strip() 352 353 _check(root[1].tag == 'origin') 354 file_origin = _etree_to_dict(root[1])['origin'] 355 356 deltas = [] 357 names = [] 358 idl = [] 359 for i in range(5, len(pobs)): 360 delta, name, idx = _import_rdata(pobs[i]) 361 deltas.append(delta) 362 if separator_insertion is None: 363 pass 364 elif isinstance(separator_insertion, int): 365 name = name[:separator_insertion] + '|' + name[separator_insertion:] 366 elif isinstance(separator_insertion, str): 367 name = name.replace(separator_insertion, f"|{separator_insertion}") 368 else: 369 raise TypeError(f"separator_insertion has to be string or int, is {type(separator_insertion).__name__}") 370 names.append(name) 371 idl.append(idx) 372 res = [Obs([d[i] for d in deltas], names, idl=idl) for i in range(len(deltas[0]))] 373 374 descriptiond = {} 375 for i in range(4): 376 descriptiond[pobs[i].tag] = pobs[i].text.strip() 377 378 _check(pobs[4].tag == "nr") 379 380 _check(pobs[5].tag == 'array') 381 if pobs[5][1].tag == 'symbol': 382 symbol = pobs[5][1].text.strip() 383 descriptiond['symbol'] = symbol 384 385 if full_output: 386 retd = {} 387 tool = file_origin.get('tool', None) 388 if tool: 389 program = tool['name'] + ' ' + tool['version'] 390 else: 391 program = '' 392 retd['program'] = program 393 retd['version'] = version 394 retd['who'] = file_origin['who'] 395 retd['date'] = file_origin['date'] 396 retd['host'] = file_origin['host'] 397 retd['description'] = descriptiond 398 retd['obsdata'] = res 399 return retd 400 else: 401 return res
Import a list of Obs from an xml.gz file in the Zeuthen pobs format.
Tags are not written or recovered automatically.
Parameters
- fname (str): Filename of the input file.
- full_output (bool): If True, a dict containing auxiliary information and the data is returned. If False, only the data is returned as list.
- separatior_insertion (str or int): str: replace all occurences of "separator_insertion" within the replica names by "|%s" % (separator_insertion) when constructing the names of the replica. int: Insert the separator "|" at the position given by separator_insertion. None (default): Replica names remain unchanged.
Returns
- res (list[Obs]): Imported data
- or
- res (dict): Imported data and meta-data
405def import_dobs_string(content, full_output=False, separator_insertion=True): 406 """Import a list of Obs from a string in the Zeuthen dobs format. 407 408 Tags are not written or recovered automatically. 409 410 Parameters 411 ---------- 412 content : str 413 XML string containing the data 414 full_output : bool 415 If True, a dict containing auxiliary information and the data is returned. 416 If False, only the data is returned as list. 417 separatior_insertion: str, int or bool 418 str: replace all occurences of "separator_insertion" within the replica names 419 by "|%s" % (separator_insertion) when constructing the names of the replica. 420 int: Insert the separator "|" at the position given by separator_insertion. 421 True (default): separator "|" is inserted after len(ensname), assuming that the 422 ensemble name is a prefix to the replica name. 423 None or False: No separator is inserted. 424 425 Returns 426 ------- 427 res : list[Obs] 428 Imported data 429 or 430 res : dict 431 Imported data and meta-data 432 """ 433 434 root = et.fromstring(content) 435 436 _check(root.tag == 'OBSERVABLES') 437 _check(root[0].tag == 'SCHEMA') 438 version = root[0][1].text.strip() 439 440 _check(root[1].tag == 'origin') 441 file_origin = _etree_to_dict(root[1])['origin'] 442 443 _check(root[2].tag == 'dobs') 444 445 dobs = root[2] 446 447 descriptiond = {} 448 for i in range(3): 449 descriptiond[dobs[i].tag] = dobs[i].text.strip() 450 451 _check(dobs[3].tag == 'array') 452 453 symbol = [] 454 if dobs[3][1].tag == 'symbol': 455 symbol = dobs[3][1].text.strip() 456 descriptiond['symbol'] = symbol 457 mean = _import_array(dobs[3])[0] 458 459 _check(dobs[4].tag == "ne") 460 ne = int(dobs[4].text.strip()) 461 _check(dobs[5].tag == "nc") 462 463 idld = {} 464 deltad = {} 465 covd = {} 466 gradd = {} 467 names = [] 468 e_names = [] 469 enstags = {} 470 for k in range(6, len(list(dobs))): 471 if dobs[k].tag == "edata": 472 _check(dobs[k][0].tag == "enstag") 473 ename = dobs[k][0].text.strip() 474 e_names.append(ename) 475 _check(dobs[k][1].tag == "nr") 476 R = int(dobs[k][1].text.strip()) 477 for i in range(2, 2 + R): 478 deltas, rname, idx = _import_rdata(dobs[k][i]) 479 if separator_insertion is None or False: 480 pass 481 elif separator_insertion is True: 482 if rname.startswith(ename): 483 rname = rname[:len(ename)] + '|' + rname[len(ename):] 484 elif isinstance(separator_insertion, int): 485 rname = rname[:separator_insertion] + '|' + rname[separator_insertion:] 486 elif isinstance(separator_insertion, str): 487 rname = rname.replace(separator_insertion, f"|{separator_insertion}") 488 else: 489 raise TypeError(f"separator_insertion has to be string or int, is {type(separator_insertion).__name__}") 490 if '|' in rname: 491 new_ename = rname[:rname.index('|')] 492 else: 493 new_ename = ename 494 enstags[new_ename] = ename 495 idld[rname] = idx 496 deltad[rname] = deltas 497 names.append(rname) 498 elif dobs[k].tag == "cdata": 499 cname, cov, grad = _import_cdata(dobs[k]) 500 covd[cname] = cov 501 if grad.shape[1] == 1: 502 gradd[cname] = [grad for i in range(len(mean))] 503 else: 504 gradd[cname] = grad.T 505 else: 506 _check(False) 507 names = list(set(names)) 508 509 for name in names: 510 for i in range(len(deltad[name])): 511 tmp = np.zeros_like(deltad[name][i]) 512 for j in range(len(deltad[name][i])): 513 if deltad[name][i][j] != 0.: 514 tmp[j] = deltad[name][i][j] + mean[i] 515 deltad[name][i] = tmp 516 517 res = [] 518 for i in range(len(mean)): 519 deltas = [] 520 idl = [] 521 obs_names = [] 522 for name in names: 523 h = np.unique(deltad[name][i]) 524 if len(h) == 1 and np.all(h == mean[i]): 525 continue 526 repdeltas = [] 527 repidl = [] 528 for j in range(len(deltad[name][i])): 529 if deltad[name][i][j] != 0.: 530 repdeltas.append(deltad[name][i][j]) 531 repidl.append(idld[name][j]) 532 if len(repdeltas) > 0: 533 obs_names.append(name) 534 deltas.append(repdeltas) 535 idl.append(repidl) 536 537 obsmeans = [np.average(deltas[j]) for j in range(len(deltas))] 538 res.append(Obs([np.array(deltas[j]) - obsmeans[j] for j in range(len(obsmeans))], obs_names, idl=idl, means=obsmeans)) 539 res[-1]._value = mean[i] 540 _check(len(e_names) == ne) 541 542 cnames = list(covd.keys()) 543 for i in range(len(res)): 544 new_covobs = {name: Covobs(0, covd[name], name, grad=gradd[name][i]) for name in cnames} 545 for name in cnames: 546 if np.all(new_covobs[name].grad == 0): 547 del new_covobs[name] 548 cnames_loc = list(new_covobs.keys()) 549 for name in cnames_loc: 550 res[i].names.append(name) 551 res[i].shape[name] = 1 552 res[i].idl[name] = [] 553 res[i]._covobs = new_covobs 554 555 if symbol: 556 for i in range(len(res)): 557 res[i].tag = symbol[i] 558 if res[i].tag == 'None': 559 res[i].tag = None 560 if full_output: 561 retd = {} 562 tool = file_origin.get('tool', None) 563 if tool: 564 program = tool['name'] + ' ' + tool['version'] 565 else: 566 program = '' 567 retd['program'] = program 568 retd['version'] = version 569 retd['who'] = file_origin['who'] 570 retd['date'] = file_origin['date'] 571 retd['host'] = file_origin['host'] 572 retd['description'] = descriptiond 573 retd['enstags'] = enstags 574 retd['obsdata'] = res 575 return retd 576 else: 577 return res
Import a list of Obs from a string in the Zeuthen dobs format.
Tags are not written or recovered automatically.
Parameters
- content (str): XML string containing the data
- full_output (bool): If True, a dict containing auxiliary information and the data is returned. If False, only the data is returned as list.
- separatior_insertion (str, int or bool): str: replace all occurences of "separator_insertion" within the replica names by "|%s" % (separator_insertion) when constructing the names of the replica. int: Insert the separator "|" at the position given by separator_insertion. True (default): separator "|" is inserted after len(ensname), assuming that the ensemble name is a prefix to the replica name. None or False: No separator is inserted.
Returns
- res (list[Obs]): Imported data
- or
- res (dict): Imported data and meta-data
580def read_dobs(fname, full_output=False, gz=True, separator_insertion=True): 581 """Import a list of Obs from an xml.gz file in the Zeuthen dobs format. 582 583 Tags are not written or recovered automatically. 584 585 Parameters 586 ---------- 587 fname : str 588 Filename of the input file. 589 full_output : bool 590 If True, a dict containing auxiliary information and the data is returned. 591 If False, only the data is returned as list. 592 gz : bool 593 If True, assumes that data is gzipped. If False, assumes XML file. 594 separatior_insertion: str, int or bool 595 str: replace all occurences of "separator_insertion" within the replica names 596 by "|%s" % (separator_insertion) when constructing the names of the replica. 597 int: Insert the separator "|" at the position given by separator_insertion. 598 True (default): separator "|" is inserted after len(ensname), assuming that the 599 ensemble name is a prefix to the replica name. 600 None or False: No separator is inserted. 601 602 Returns 603 ------- 604 res : list[Obs] 605 Imported data 606 or 607 res : dict 608 Imported data and meta-data 609 """ 610 611 if not fname.endswith('.xml') and not fname.endswith('.gz'): 612 fname += '.xml' 613 if gz: 614 if not fname.endswith('.gz'): 615 fname += '.gz' 616 with gzip.open(fname, 'r') as fin: 617 content = fin.read() 618 else: 619 if fname.endswith('.gz'): 620 warnings.warn(f"Trying to read from {fname} without unzipping!", UserWarning, stacklevel=2) 621 with open(fname) as fin: 622 content = fin.read() 623 624 return import_dobs_string(content, full_output, separator_insertion=separator_insertion)
Import a list of Obs from an xml.gz file in the Zeuthen dobs format.
Tags are not written or recovered automatically.
Parameters
- fname (str): Filename of the input file.
- full_output (bool): If True, a dict containing auxiliary information and the data is returned. If False, only the data is returned as list.
- gz (bool): If True, assumes that data is gzipped. If False, assumes XML file.
- separatior_insertion (str, int or bool): str: replace all occurences of "separator_insertion" within the replica names by "|%s" % (separator_insertion) when constructing the names of the replica. int: Insert the separator "|" at the position given by separator_insertion. True (default): separator "|" is inserted after len(ensname), assuming that the ensemble name is a prefix to the replica name. None or False: No separator is inserted.
Returns
- res (list[Obs]): Imported data
- or
- res (dict): Imported data and meta-data
686def create_dobs_string(obsl, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None): 687 """Generate the string for the export of a list of Obs or structures containing Obs 688 to a .xml.gz file according to the Zeuthen dobs format. 689 690 Tags are not written or recovered automatically. The separator |is removed from the replica names. 691 692 Parameters 693 ---------- 694 obsl : list 695 List of Obs that will be exported. 696 The Obs inside a structure do not have to be defined on the same set of configurations, 697 but the storage requirement is increased, if this is not the case. 698 name : str 699 The name of the observable. 700 spec : str 701 Optional string that describes the contents of the file. 702 origin : str 703 Specify where the data has its origin. 704 symbol : list 705 A list of symbols that describe the observables to be written. May be empty. 706 who : str 707 Provide the name of the person that exports the data. 708 enstags : dict 709 Provide alternative enstag for ensembles in the form enstags = {ename: enstag} 710 Otherwise, the ensemble name is used. 711 712 Returns 713 ------- 714 xml_str : str 715 XML string generated from the data 716 """ 717 if enstags is None: 718 enstags = {} 719 if symbol is None: 720 symbol = [] 721 od = {} 722 r_names = [] 723 for o in obsl: 724 r_names += [name for name in o.names if name.split('|')[0] in o.mc_names] 725 r_names = sorted(set(r_names)) 726 mc_names = sorted(set([n.split('|')[0] for n in r_names])) 727 for tmpname in mc_names: 728 if tmpname not in enstags: 729 enstags[tmpname] = tmpname 730 ne = len(set(mc_names)) 731 cov_names = [] 732 for o in obsl: 733 cov_names += list(o.cov_names) 734 cov_names = sorted(set(cov_names)) 735 nc = len(set(cov_names)) 736 od['OBSERVABLES'] = {} 737 od['OBSERVABLES']['SCHEMA'] = {'NAME': 'lattobs', 'VERSION': '1.0'} 738 if who is None: 739 who = getpass.getuser() 740 od['OBSERVABLES']['origin'] = { 741 'who': who, 742 'date': str(datetime.datetime.now())[:-7], 743 'host': socket.gethostname(), 744 'tool': {'name': 'pyerrors', 'version': pyerrorsversion.__version__}} 745 od['OBSERVABLES']['dobs'] = {} 746 pd = od['OBSERVABLES']['dobs'] 747 pd['spec'] = spec 748 pd['origin'] = origin 749 pd['name'] = name 750 pd['array'] = {} 751 pd['array']['id'] = 'val' 752 pd['array']['layout'] = f'1 f{len(obsl)}' 753 osymbol = '' 754 if symbol: 755 if not isinstance(symbol, list): 756 raise TypeError('Symbol has to be a list!') 757 if not (len(symbol) == 0 or len(symbol) == len(obsl)): 758 raise ValueError(f'Symbol has to be a list of length 0 or {len(obsl)}!') 759 osymbol = symbol[0] 760 for s in symbol[1:]: 761 osymbol += f' {s}' 762 pd['array']['symbol'] = osymbol 763 764 pd['array']['#values'] = [' '.join([f'{o.value:1.16e}' for o in obsl])] 765 pd['ne'] = f'{ne}' 766 pd['nc'] = f'{nc}' 767 pd['edata'] = [] 768 for name in mc_names: 769 ed = {} 770 ed['enstag'] = enstags[name] 771 onames = sorted([n for n in r_names if (n.startswith(name + '|') or n == name)]) 772 nr = len(onames) 773 ed['nr'] = nr 774 ed[''] = [] 775 776 for r in range(nr): 777 ad = {} 778 repname = onames[r] 779 ad['id'] = repname.replace('|', '') 780 idx = _merge_idx([o.idl.get(repname, []) for o in obsl]) 781 Nconf = len(idx) 782 layout = f'{Nconf} i f{len(obsl)}' 783 ad['layout'] = layout 784 data = '' 785 counters = [0 for o in obsl] 786 offsets = [o.r_values[repname] - o.value if repname in o.r_values else 0 for o in obsl] 787 for ci in idx: 788 data += f'{ci} ' 789 for oi in range(len(obsl)): 790 o = obsl[oi] 791 if repname in o.idl: 792 if counters[oi] < 0: 793 num = 0 794 if num == 0: 795 data += '0 ' 796 else: 797 data += f'{num:1.16e} ' 798 continue 799 if o.idl[repname][counters[oi]] == ci: 800 num = o.deltas[repname][counters[oi]] + offsets[oi] 801 if num == 0: 802 data += '0 ' 803 else: 804 data += f'{num:1.16e} ' 805 counters[oi] += 1 806 if counters[oi] >= len(o.idl[repname]): 807 counters[oi] = -1 808 else: 809 num = 0 810 if num == 0: 811 data += '0 ' 812 else: 813 data += f'{num:1.16e} ' 814 else: 815 data += '0 ' 816 data += '\n' 817 ad['#data'] = data 818 ed[''].append(ad) 819 pd['edata'].append(ed) 820 821 allcov = {} 822 for o in obsl: 823 for cname in o.cov_names: 824 if cname in allcov: 825 if not np.array_equal(allcov[cname], o.covobs[cname].cov): 826 raise Exception(f'Inconsistent covariance matrices for {cname}!') 827 else: 828 allcov[cname] = o.covobs[cname].cov 829 pd['cdata'] = [] 830 for cname in cov_names: 831 cd = {} 832 cd['id'] = cname 833 834 covd = {'id': 'cov'} 835 if allcov[cname].shape == (): 836 ncov = 1 837 covd['layout'] = '1 1 f' 838 covd['#data'] = f'{allcov[cname]:1.14e}' 839 else: 840 shape = allcov[cname].shape 841 assert (shape[0] == shape[1]) 842 ncov = shape[0] 843 covd['layout'] = f'{ncov} {ncov} f' 844 ds = '' 845 for i in range(ncov): 846 for j in range(ncov): 847 val = allcov[cname][i][j] 848 if val == 0: 849 ds += '0 ' 850 else: 851 ds += f'{val:1.14e} ' 852 ds += '\n' 853 covd['#data'] = ds 854 855 gradd = {'id': 'grad'} 856 gradd['layout'] = f'{ncov} f{len(obsl)}' 857 ds = '' 858 for i in range(ncov): 859 for o in obsl: 860 if cname in o.covobs: 861 val = o.covobs[cname].grad[i].item() 862 if val != 0: 863 ds += f'{val:1.14e} ' 864 else: 865 ds += '0 ' 866 else: 867 ds += '0 ' 868 gradd['#data'] = ds 869 cd['array'] = [covd, gradd] 870 pd['cdata'].append(cd) 871 872 rs = '<?xml version="1.0" encoding="utf-8"?>\n' + _dobsdict_to_xmlstring_spaces(od) 873 874 return rs
Generate the string for the export of a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen dobs format.
Tags are not written or recovered automatically. The separator |is removed from the replica names.
Parameters
- obsl (list): List of Obs that will be exported. The Obs inside a structure do not have to be defined on the same set of configurations, but the storage requirement is increased, if this is not the case.
- name (str): The name of the observable.
- spec (str): Optional string that describes the contents of the file.
- origin (str): Specify where the data has its origin.
- symbol (list): A list of symbols that describe the observables to be written. May be empty.
- who (str): Provide the name of the person that exports the data.
- enstags (dict): Provide alternative enstag for ensembles in the form enstags = {ename: enstag} Otherwise, the ensemble name is used.
Returns
- xml_str (str): XML string generated from the data
877def write_dobs(obsl, fname, name, spec='dobs v1.0', origin='', symbol=None, who=None, enstags=None, gz=True): 878 """Export a list of Obs or structures containing Obs to a .xml.gz file 879 according to the Zeuthen dobs format. 880 881 Tags are not written or recovered automatically. The separator | is removed from the replica names. 882 883 Parameters 884 ---------- 885 obsl : list 886 List of Obs that will be exported. 887 The Obs inside a structure do not have to be defined on the same set of configurations, 888 but the storage requirement is increased, if this is not the case. 889 fname : str 890 Filename of the output file. 891 name : str 892 The name of the observable. 893 spec : str 894 Optional string that describes the contents of the file. 895 origin : str 896 Specify where the data has its origin. 897 symbol : list 898 A list of symbols that describe the observables to be written. May be empty. 899 who : str 900 Provide the name of the person that exports the data. 901 enstags : dict 902 Provide alternative enstag for ensembles in the form enstags = {ename: enstag} 903 Otherwise, the ensemble name is used. 904 gz : bool 905 If True, the output is a gzipped XML. If False, the output is a XML file. 906 907 Returns 908 ------- 909 None 910 """ 911 if enstags is None: 912 enstags = {} 913 914 dobsstring = create_dobs_string(obsl, name, spec, origin, symbol, who, enstags=enstags) 915 916 if not fname.endswith('.xml') and not fname.endswith('.gz'): 917 fname += '.xml' 918 919 if gz: 920 if not fname.endswith('.gz'): 921 fname += '.gz' 922 923 fp = gzip.open(fname, 'wb') 924 fp.write(dobsstring.encode('utf-8')) 925 else: 926 fp = open(fname, 'w', encoding='utf-8') 927 fp.write(dobsstring) 928 fp.close()
Export a list of Obs or structures containing Obs to a .xml.gz file according to the Zeuthen dobs format.
Tags are not written or recovered automatically. The separator | is removed from the replica names.
Parameters
- obsl (list): List of Obs that will be exported. The Obs inside a structure do not have to be defined on the same set of configurations, but the storage requirement is increased, if this is not the case.
- fname (str): Filename of the output file.
- name (str): The name of the observable.
- spec (str): Optional string that describes the contents of the file.
- origin (str): Specify where the data has its origin.
- symbol (list): A list of symbols that describe the observables to be written. May be empty.
- who (str): Provide the name of the person that exports the data.
- enstags (dict): Provide alternative enstag for ensembles in the form enstags = {ename: enstag} Otherwise, the ensemble name is used.
- gz (bool): If True, the output is a gzipped XML. If False, the output is a XML file.
Returns
- None