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
« 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/>.
20import textwrap 1cab
22import gvar 1cab
23import numpy 1cab
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.
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.
48 Returns
49 -------
50 table : str
51 The formatted table.
53 See Also
54 --------
55 gvar.tabulate
57 Examples
58 --------
59 >>> print(tabulate_together(gvar.gvar(dict(a=1)), gvar.gvar(dict(a=2))))
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
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
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
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