Coverage for src/lsqfitgp/_gvarext/_tabulate.py: 68%

48 statements  

« prev     ^ index     » next       coverage.py v7.16.1, created at 2026-10-04 11:43 +0000

1# lsqfitgp/_gvarext/_tabulate.py 

2# 

3# Copyright (c) 2023, 2026, Giacomo Petrillo 

4# 

5# This file is part of lsqfitgp. 

6# 

7# lsqfitgp is free software: you can redistribute it and/or modify 

8# it under the terms of the GNU General Public License as published by 

9# the Free Software Foundation, either version 3 of the License, or 

10# (at your option) any later version. 

11# 

12# lsqfitgp is distributed in the hope that it will be useful, 

13# but WITHOUT ANY WARRANTY; without even the implied warranty of 

14# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

15# GNU General Public License for more details. 

16# 

17# You should have received a copy of the GNU General Public License 

18# along with lsqfitgp. If not, see <http://www.gnu.org/licenses/>. 

19 

20import textwrap 1cab

21 

22import gvar 1cab

23import numpy 1cab

24 

25 

26def tabulate_together(*gs, headers=True, offset='', ndecimal=None, keys=None): 1cab

27 """ 

28 Format a table comparing side by side various collections of gvars. 

29 

30 Parameters 

31 ---------- 

32 *gs : sequence of arrays or dictionaries of gvars 

33 The variables to be tabulated. The structures of arrays and dictionaries 

34 must match. 

35 headers : bool or sequence of strings 

36 If True (default), add automatically an header. If False, don't add an 

37 header. If a sequence with length len(gs) + 1, it contains the column 

38 names for the keys/indices and for each set of variables. 

39 offset : str 

40 Prefix to each line, default empty. 

41 ndecimal : int, optional 

42 Number of decimal places. If not specified (default), keep two error 

43 digits. 

44 keys : sequence, optional 

45 If ``gs`` are dictionaries, a subset of keys to be extracted from each 

46 dictionary. Ignored if they are arrays. 

47 

48 Returns 

49 ------- 

50 table : str 

51 The formatted table. 

52 

53 See Also 

54 -------- 

55 gvar.tabulate 

56 

57 Examples 

58 -------- 

59 >>> print(tabulate_together(gvar.gvar(dict(a=1)), gvar.gvar(dict(a=2)))) 

60 

61 key/index value1 value2 

62 --------------------------- 

63 a 1 (0) 2 (0) 

64 """ # noqa: DOC001 

65 if not gs: 65 ↛ 66line 65 didn't jump to line 66 because the condition on line 65 was never true1ab

66 return '' 

67 gs = [g if hasattr(g, 'keys') else numpy.asarray(g) for g in gs] 1ab

68 assert all(hasattr(g, 'keys') for g in gs) or all( 1ab

69 not hasattr(g, 'keys') for g in gs 

70 ) 

71 if keys is not None and hasattr(gs[0], 'keys'): 71 ↛ 72line 71 didn't jump to line 72 because the condition on line 71 was never true1ab

72 gs = [{k: g[k] for k in keys} for g in gs] 

73 g0 = gs[0] 1ab

74 if hasattr(g0, 'keys'): 74 ↛ 78line 74 didn't jump to line 78 because the condition on line 74 was always true1ab

75 assert all(set(g.keys()) == set(g0.keys()) for g in gs[1:]) 1ab

76 gs = [{k: g[k] for k in g0} for g in gs] 1ab

77 else: 

78 assert all(g.shape == g0.shape for g in gs[1:]) 

79 if g0.shape == (): 

80 gs = [{'--': g} for g in gs] 

81 tables = [ 1ab

82 _splittable(gvar.tabulate(g, headers=['@', ''], ndecimal=ndecimal)) for g in gs 

83 ] 

84 columns = list(tables[0]) + [t[1] for t in tables[1:]] 1ab

85 if not hasattr(headers, '__len__'): 85 ↛ 86line 85 didn't jump to line 86 because the condition on line 85 was never true1ab

86 if headers: 

87 headers = ['key/index'] + [f'value{i + 1}' for i in range(len(gs))] 

88 else: 

89 headers = None 

90 else: 

91 assert len(headers) == len(columns) 1ab

92 if headers is not None: 92 ↛ 94line 92 didn't jump to line 94 because the condition on line 92 was always true1ab

93 columns = (_head(col, head) for col, head in zip(columns, headers, strict=True)) 1ab

94 return textwrap.indent(_join(columns), offset) 1ab

95 

96 

97def _splittable(table): 1cab

98 lines = table.split('\n') 1ab

99 header = lines[0] 1ab

100 col = header.find('@') + 1 1ab

101 contentlines = lines[2:] 1ab

102 col1 = '\n'.join(line[:col] for line in contentlines) 1ab

103 col2 = '\n'.join(line[col:] for line in contentlines) 1ab

104 return col1, col2 1ab

105 

106 

107def _head(col, head): 1cab

108 head = str(head) 1ab

109 width = col.find('\n') 1ab

110 if width < 0: 110 ↛ 111line 110 didn't jump to line 111 because the condition on line 110 was never true1ab

111 width = len(col) 

112 hwidth = len(head) 1ab

113 if hwidth > width: 113 ↛ 114line 113 didn't jump to line 114 because the condition on line 113 was never true1ab

114 col = textwrap.indent(col, (hwidth - width) * ' ') 

115 else: 

116 head = (width - hwidth) * ' ' + head 1ab

117 return head + '\n' + len(head) * '-' + '\n' + col 1ab

118 

119 

120def _join(cols): 1cab

121 split = (col.split('\n') for col in cols) 1ab

122 return '\n'.join(''.join(lines) for lines in zip(*split, strict=True)) 1ab