qrotor.systems
Description
This module contains utility functions to handle multiple qrotor.system calculations.
These are commonly used as a list of System objects.
Index
as_list() |
Ensures that a list only contains System objects |
save_energies() |
Save the energy eigenvalues for all systems to a CSV |
save_splittings() |
Save the tunnel splitting energies for all systems to a CSV |
save_summary() |
Save a summary of some relevant parameters for all systems to a CSV |
get_energies() |
Get the eigenvalues from all systems |
get_gridsizes() |
Get all gridsizes |
get_runtimes() |
Get all runtimes |
get_tags() |
Get a list with all system tags |
filter_tags() |
Filter the systems with or without specific tags |
calculate_ideal_E() |
Calculate the ideal energy for a specified level |
sort_by_gridsize() |
Sort systems by gridsize |
reduce_size() |
Discard data that takes too much space |
summary() |
Print a summary of a System or list of Systems |
1""" 2# Description 3 4This module contains utility functions to handle multiple `qrotor.system` calculations. 5These are commonly used as a list of `System` objects. 6 7 8# Index 9 10| | | 11| --- | --- | 12| `as_list()` | Ensures that a list only contains System objects | 13| `save_energies()` | Save the energy eigenvalues for all systems to a CSV | 14| `save_splittings()` | Save the tunnel splitting energies for all systems to a CSV | 15| `save_summary()` | Save a summary of some relevant parameters for all systems to a CSV | 16| `get_energies()` | Get the eigenvalues from all systems | 17| `get_gridsizes()` | Get all gridsizes | 18| `get_runtimes()` | Get all runtimes | 19| `get_tags()` | Get a list with all system tags | 20| `filter_tags()` | Filter the systems with or without specific tags | 21| `calculate_ideal_E()` | Calculate the ideal energy for a specified level | 22| `sort_by_gridsize()` | Sort systems by gridsize | 23| `reduce_size()` | Discard data that takes too much space | 24| `summary()` | Print a summary of a System or list of Systems | 25 26--- 27""" 28 29 30from .system import System 31from aton import txt 32import pandas as pd 33 34 35def as_list(systems) -> None: 36 """Ensures that `systems` is a list of System objects. 37 38 If it is a System, returns a list with that System as the only element. 39 If it is neither a list nor a System, 40 or if the list does not contain only System objects, 41 it raises an error. 42 """ 43 if isinstance(systems, System): 44 systems = [systems] 45 if not isinstance(systems, list): 46 raise TypeError(f"Must be a System object or a list of systems, found instead: {type(systems)}") 47 for i in systems: 48 if not isinstance(i, System): 49 raise TypeError(f"All items in the list must be System objects, found instead: {type(i)}") 50 return systems 51 52 53def save_energies( 54 systems:list, 55 comment:str='', 56 filepath:str='qrotor_eigenvalues.csv', 57 ) -> pd.DataFrame: 58 """Save the energy eigenvalues for all `systems` to a qrotor_eigenvalues.csv file. 59 60 Returns a Pandas Dataset with `System.comment` columns and `System.eigenvalues` values. 61 62 The output file can be changed with `filepath`, 63 or set to null to avoid saving the dataset. 64 A `comment` can be included at the top of the file. 65 Note that `System.comment` must not include commas (`,`). 66 """ 67 systems = as_list(systems) 68 version = systems[0].version 69 E = {} 70 # Find max length of eigenvalues 71 max_len = max((len(s.eigenvalues) if s.eigenvalues is not None else 0) for s in systems) 72 for s in systems: 73 if s.eigenvalues is not None: 74 # Filter out None values and replace with NaN 75 valid_eigenvalues = [float('nan') if e is None else e for e in s.eigenvalues] 76 padded_eigenvalues = valid_eigenvalues + [float('nan')] * (max_len - len(s.eigenvalues)) 77 else: 78 padded_eigenvalues = [float('nan')] * max_len 79 E[s.comment] = padded_eigenvalues 80 df = pd.DataFrame(E) 81 if not filepath: 82 return df 83 # Else save to file 84 df.to_csv(filepath, sep=',', index=False) 85 # Include a comment at the top of the file 86 file_comment = f'## {comment}\n' if comment else f'' 87 file_comment += f'# Energy eigenvalues\n' 88 file_comment += f'# Calculated with QRotor {version}\n' 89 file_comment += f'# https://pablogila.github.io/qrotor\n#' 90 txt.edit.insert_at(filepath, file_comment, 0) 91 print(f'Energy eigenvalues saved to {filepath}') 92 return df 93 94 95def save_splittings( 96 systems:list, 97 comment:str='', 98 filepath:str='qrotor_splittings.csv', 99 ) -> pd.DataFrame: 100 """Save the tunnel splitting energies for all `systems` to a qrotor_splittings.csv file. 101 102 Returns a Pandas Dataset with `System.comment` columns and `System.splittings` values. 103 104 The output file can be changed with `filepath`, 105 or set to null to avoid saving the dataset. 106 A `comment` can be included at the top of the file. 107 Note that `System.comment` must not include commas (`,`). 108 Different splitting lengths across systems are allowed - missing values will be NaN. 109 """ 110 systems = as_list(systems) 111 version = systems[0].version 112 tunnelling_E = {} 113 # Find max length of splittings 114 max_len = max(len(s.splittings) for s in systems) 115 for s in systems: # Pad shorter splittings with NaN 116 padded_splittings = s.splittings + [float('nan')] * (max_len - len(s.splittings)) 117 tunnelling_E[s.comment] = padded_splittings 118 df = pd.DataFrame(tunnelling_E) 119 if not filepath: 120 return df 121 # Else save to file 122 df.to_csv(filepath, sep=',', index=False) 123 # Include a comment at the top of the file 124 file_comment = f'## {comment}\n' if comment else f'' 125 file_comment += f'# Tunnel splitting energies\n' 126 file_comment += f'# Calculated with QRotor {version}\n' 127 file_comment += f'# https://pablogila.github.io/qrotor\n#' 128 txt.edit.insert_at(filepath, file_comment, 0) 129 print(f'Tunnel splitting energies saved to {filepath}') 130 return df 131 132 133def save_summary( 134 systems:list, 135 comment:str='', 136 filepath:str='qrotor_summary.csv', 137 ) -> pd.DataFrame: 138 """Save a summary for all `systems` to a qrotor_summary.csv file. 139 140 Produces one row per System with the columns: 141 `comment`, `ZPE`, `E_activation`, `potential_max`, `1st_splitting`, 142 `1st_excitation`, `B`, `degeneracy`, `gridsize`. 143 144 Set `filepath` to null to just return the DataFrame. 145 """ 146 systems = as_list(systems) 147 version = systems[0].version 148 rows = [] 149 for s in systems: 150 eigenvalues = getattr(s, 'eigenvalues', None) 151 if eigenvalues is not None and len(eigenvalues) > 0: 152 first_val = eigenvalues[0] 153 zpe = float('nan') if first_val is None else first_val 154 else: 155 zpe = float('nan') 156 splittings = getattr(s, 'splittings', None) 157 if splittings is not None and len(splittings) > 0: 158 first_splitting = float('nan') if splittings[0] is None else splittings[0] 159 else: 160 first_splitting = float('nan') 161 excitations = getattr(s, 'excitations', None) 162 if excitations is not None and len(excitations) > 0: 163 first_excitation = float('nan') if excitations[0] is None else excitations[0] 164 else: 165 first_excitation = float('nan') 166 system_comment = getattr(s, 'comment', None) 167 E_activation = getattr(s, 'E_activation', None) 168 B = getattr(s, 'B', None) 169 tags = getattr(s, 'tags', None) 170 deg = getattr(s, 'deg', None) 171 gridsize = getattr(s, 'gridsize', None) 172 potential_max = getattr(s, 'potential_max', None) 173 # Each row contains the following: 174 rows.append({ 175 'comment': system_comment, 176 'ZPE': zpe, 177 'potential_max': potential_max, 178 'E_activation': E_activation, 179 '1st_splitting': first_splitting, 180 '1st_excitation': first_excitation, 181 'B': B, 182 'degeneracy': deg, 183 'gridsize': gridsize, 184 'tags': tags, 185 }) 186 # Save to file or just return df 187 df = pd.DataFrame(rows) 188 if not filepath: 189 return df 190 df.to_csv(filepath, sep=',', index=False) 191 # Include a comment at the top of the file 192 file_comment = f'## {comment}\n' if comment else '' 193 file_comment += '# Summary of systems\n' 194 file_comment += f'# Calculated with QRotor {version}\n' 195 file_comment += '# https://pablogila.github.io/qrotor\n#' 196 txt.edit.insert_at(filepath, file_comment, 0) 197 print(f'Summary saved to {filepath}') 198 return df 199 200 201def get_energies(systems:list) -> list: 202 """Get a list with all lists of eigenvalues from all systems. 203 204 If no eigenvalues are present for a particular system, appends None. 205 """ 206 systems = as_list(systems) 207 energies = [] 208 for i in systems: 209 if all(i.eigenvalues): 210 energies.append(i.eigenvalues) 211 else: 212 energies.append(None) 213 return energies 214 215 216def get_gridsizes(systems:list) -> list: 217 """Get a list with all gridsize values. 218 219 If no gridsize value is present for a particular system, appends None. 220 """ 221 systems = as_list(systems) 222 gridsizes = [] 223 for i in systems: 224 if i.gridsize: 225 gridsizes.append(i.gridsize) 226 elif any(i.potential_values): 227 gridsizes.append(len(i.potential_values)) 228 else: 229 gridsizes.append(None) 230 return gridsizes 231 232 233def get_runtimes(systems:list) -> list: 234 """Returns a list with all runtime values. 235 236 If no runtime value is present for a particular system, appends None. 237 """ 238 systems = as_list(systems) 239 runtimes = [] 240 for i in systems: 241 if i.runtime: 242 runtimes.append(i.runtime) 243 else: 244 runtimes.append(None) 245 return runtimes 246 247 248def get_tags(systems:list) -> list: 249 """Returns a list with all system tags.""" 250 systems = as_list(systems) 251 tags = [] 252 for i in systems: 253 # i.tags is guaranteed to exist and be a string (may be empty) 254 system_tags = i.tags.split() 255 for tag in system_tags: 256 if tag not in tags: 257 tags.append(tag) 258 return tags 259 260 261def filter_tags( 262 systems:list, 263 include:str='', 264 exclude:str='', 265 strict:bool=False, 266 ) -> list: 267 """Returns a filtered list of systems with or without specific tags. 268 269 You can `include` or `exclude` any number of tags, separated by blank spaces. 270 By default, the filters are triggered if any tag is found, i.e. *tag1 OR tag2*. 271 Set `strict=True` to require all tags to match, i.e. *tag1 AND tag2*. 272 """ 273 systems = as_list(systems) 274 included_tags = include.split() 275 excluded_tags = exclude.split() 276 filtered_systems = [] 277 for i in systems: 278 tags_found = get_tags(i) 279 if excluded_tags: 280 if strict and all(tag in tags_found for tag in excluded_tags): 281 continue 282 elif not strict and any(tag in tags_found for tag in excluded_tags): 283 continue 284 if included_tags: 285 if strict and not all(tag in tags_found for tag in included_tags): 286 continue 287 elif not strict and not any(tag in tags_found for tag in included_tags): 288 continue 289 filtered_systems.append(i) 290 return filtered_systems 291 292 293def calculate_ideal_E(E_level:int) -> int: 294 """Calculates the ideal energy for a specified `E_level`. 295 296 To be used in convergence tests with `potential_name = 'zero'`. 297 """ 298 real_E_level = None 299 if E_level % 2 == 0: 300 real_E_level = E_level / 2 301 else: 302 real_E_level = (E_level + 1) / 2 303 ideal_E = int(real_E_level ** 2) 304 return ideal_E 305 306 307def sort_by_gridsize(systems:list) -> list: 308 """Sorts a list of System objects by `System.gridsize`.""" 309 systems = as_list(systems) 310 systems = sorted(systems, key=lambda sys: sys.gridsize) 311 return systems 312 313 314def reduce_size(systems:list) -> list: 315 """Discard data that takes too much space. 316 317 Removes eigenvectors, potential values and grids, 318 for all System values inside the `systems` list. 319 """ 320 systems = as_list(systems) 321 for dataset in systems: 322 dataset = dataset.reduce_size() 323 return systems 324 325 326def summary( 327 systems, 328 verbose:bool=False 329 ) -> None: 330 """Print a summary of a System or list of Systems. 331 332 Print extra info with `verbose=True` 333 """ 334 print('--------------------') 335 systems = as_list(systems) 336 337 for system in systems: 338 dictionary = system.summary() 339 if verbose: 340 for key, value in dictionary.items(): 341 print(f'{key:<24}', value) 342 else: 343 eigenvalues = system.eigenvalues if any(system.eigenvalues) else [] 344 extra = '' 345 if len(system.eigenvalues) > 6: 346 eigenvalues = eigenvalues[:6] 347 extra = '...' 348 print('comment ' + str(system.comment)) 349 print('ZPE ' + str(system.eigenvalues[0])) 350 print('E activation ' + str(system.E_activation)) 351 print('V max ' + str(system.potential_max)) 352 print('1st splitting ' + str(system.splittings[0])) 353 print('1st excitation ' + str(system.excitations[0])) 354 print('B ' + str(system.B)) 355 print('eigenvalues ' + str([float(round(e, 4)) for e in eigenvalues]) + extra) 356 print('tags ' + str(system.tags)) 357 print('version ' + str(system.version)) 358 print('--------------------') 359 return None
36def as_list(systems) -> None: 37 """Ensures that `systems` is a list of System objects. 38 39 If it is a System, returns a list with that System as the only element. 40 If it is neither a list nor a System, 41 or if the list does not contain only System objects, 42 it raises an error. 43 """ 44 if isinstance(systems, System): 45 systems = [systems] 46 if not isinstance(systems, list): 47 raise TypeError(f"Must be a System object or a list of systems, found instead: {type(systems)}") 48 for i in systems: 49 if not isinstance(i, System): 50 raise TypeError(f"All items in the list must be System objects, found instead: {type(i)}") 51 return systems
Ensures that systems is a list of System objects.
If it is a System, returns a list with that System as the only element. If it is neither a list nor a System, or if the list does not contain only System objects, it raises an error.
54def save_energies( 55 systems:list, 56 comment:str='', 57 filepath:str='qrotor_eigenvalues.csv', 58 ) -> pd.DataFrame: 59 """Save the energy eigenvalues for all `systems` to a qrotor_eigenvalues.csv file. 60 61 Returns a Pandas Dataset with `System.comment` columns and `System.eigenvalues` values. 62 63 The output file can be changed with `filepath`, 64 or set to null to avoid saving the dataset. 65 A `comment` can be included at the top of the file. 66 Note that `System.comment` must not include commas (`,`). 67 """ 68 systems = as_list(systems) 69 version = systems[0].version 70 E = {} 71 # Find max length of eigenvalues 72 max_len = max((len(s.eigenvalues) if s.eigenvalues is not None else 0) for s in systems) 73 for s in systems: 74 if s.eigenvalues is not None: 75 # Filter out None values and replace with NaN 76 valid_eigenvalues = [float('nan') if e is None else e for e in s.eigenvalues] 77 padded_eigenvalues = valid_eigenvalues + [float('nan')] * (max_len - len(s.eigenvalues)) 78 else: 79 padded_eigenvalues = [float('nan')] * max_len 80 E[s.comment] = padded_eigenvalues 81 df = pd.DataFrame(E) 82 if not filepath: 83 return df 84 # Else save to file 85 df.to_csv(filepath, sep=',', index=False) 86 # Include a comment at the top of the file 87 file_comment = f'## {comment}\n' if comment else f'' 88 file_comment += f'# Energy eigenvalues\n' 89 file_comment += f'# Calculated with QRotor {version}\n' 90 file_comment += f'# https://pablogila.github.io/qrotor\n#' 91 txt.edit.insert_at(filepath, file_comment, 0) 92 print(f'Energy eigenvalues saved to {filepath}') 93 return df
Save the energy eigenvalues for all systems to a qrotor_eigenvalues.csv file.
Returns a Pandas Dataset with System.comment columns and System.eigenvalues values.
The output file can be changed with filepath,
or set to null to avoid saving the dataset.
A comment can be included at the top of the file.
Note that System.comment must not include commas (,).
96def save_splittings( 97 systems:list, 98 comment:str='', 99 filepath:str='qrotor_splittings.csv', 100 ) -> pd.DataFrame: 101 """Save the tunnel splitting energies for all `systems` to a qrotor_splittings.csv file. 102 103 Returns a Pandas Dataset with `System.comment` columns and `System.splittings` values. 104 105 The output file can be changed with `filepath`, 106 or set to null to avoid saving the dataset. 107 A `comment` can be included at the top of the file. 108 Note that `System.comment` must not include commas (`,`). 109 Different splitting lengths across systems are allowed - missing values will be NaN. 110 """ 111 systems = as_list(systems) 112 version = systems[0].version 113 tunnelling_E = {} 114 # Find max length of splittings 115 max_len = max(len(s.splittings) for s in systems) 116 for s in systems: # Pad shorter splittings with NaN 117 padded_splittings = s.splittings + [float('nan')] * (max_len - len(s.splittings)) 118 tunnelling_E[s.comment] = padded_splittings 119 df = pd.DataFrame(tunnelling_E) 120 if not filepath: 121 return df 122 # Else save to file 123 df.to_csv(filepath, sep=',', index=False) 124 # Include a comment at the top of the file 125 file_comment = f'## {comment}\n' if comment else f'' 126 file_comment += f'# Tunnel splitting energies\n' 127 file_comment += f'# Calculated with QRotor {version}\n' 128 file_comment += f'# https://pablogila.github.io/qrotor\n#' 129 txt.edit.insert_at(filepath, file_comment, 0) 130 print(f'Tunnel splitting energies saved to {filepath}') 131 return df
Save the tunnel splitting energies for all systems to a qrotor_splittings.csv file.
Returns a Pandas Dataset with System.comment columns and System.splittings values.
The output file can be changed with filepath,
or set to null to avoid saving the dataset.
A comment can be included at the top of the file.
Note that System.comment must not include commas (,).
Different splitting lengths across systems are allowed - missing values will be NaN.
134def save_summary( 135 systems:list, 136 comment:str='', 137 filepath:str='qrotor_summary.csv', 138 ) -> pd.DataFrame: 139 """Save a summary for all `systems` to a qrotor_summary.csv file. 140 141 Produces one row per System with the columns: 142 `comment`, `ZPE`, `E_activation`, `potential_max`, `1st_splitting`, 143 `1st_excitation`, `B`, `degeneracy`, `gridsize`. 144 145 Set `filepath` to null to just return the DataFrame. 146 """ 147 systems = as_list(systems) 148 version = systems[0].version 149 rows = [] 150 for s in systems: 151 eigenvalues = getattr(s, 'eigenvalues', None) 152 if eigenvalues is not None and len(eigenvalues) > 0: 153 first_val = eigenvalues[0] 154 zpe = float('nan') if first_val is None else first_val 155 else: 156 zpe = float('nan') 157 splittings = getattr(s, 'splittings', None) 158 if splittings is not None and len(splittings) > 0: 159 first_splitting = float('nan') if splittings[0] is None else splittings[0] 160 else: 161 first_splitting = float('nan') 162 excitations = getattr(s, 'excitations', None) 163 if excitations is not None and len(excitations) > 0: 164 first_excitation = float('nan') if excitations[0] is None else excitations[0] 165 else: 166 first_excitation = float('nan') 167 system_comment = getattr(s, 'comment', None) 168 E_activation = getattr(s, 'E_activation', None) 169 B = getattr(s, 'B', None) 170 tags = getattr(s, 'tags', None) 171 deg = getattr(s, 'deg', None) 172 gridsize = getattr(s, 'gridsize', None) 173 potential_max = getattr(s, 'potential_max', None) 174 # Each row contains the following: 175 rows.append({ 176 'comment': system_comment, 177 'ZPE': zpe, 178 'potential_max': potential_max, 179 'E_activation': E_activation, 180 '1st_splitting': first_splitting, 181 '1st_excitation': first_excitation, 182 'B': B, 183 'degeneracy': deg, 184 'gridsize': gridsize, 185 'tags': tags, 186 }) 187 # Save to file or just return df 188 df = pd.DataFrame(rows) 189 if not filepath: 190 return df 191 df.to_csv(filepath, sep=',', index=False) 192 # Include a comment at the top of the file 193 file_comment = f'## {comment}\n' if comment else '' 194 file_comment += '# Summary of systems\n' 195 file_comment += f'# Calculated with QRotor {version}\n' 196 file_comment += '# https://pablogila.github.io/qrotor\n#' 197 txt.edit.insert_at(filepath, file_comment, 0) 198 print(f'Summary saved to {filepath}') 199 return df
Save a summary for all systems to a qrotor_summary.csv file.
Produces one row per System with the columns:
comment, ZPE, E_activation, potential_max, 1st_splitting,
1st_excitation, B, degeneracy, gridsize.
Set filepath to null to just return the DataFrame.
202def get_energies(systems:list) -> list: 203 """Get a list with all lists of eigenvalues from all systems. 204 205 If no eigenvalues are present for a particular system, appends None. 206 """ 207 systems = as_list(systems) 208 energies = [] 209 for i in systems: 210 if all(i.eigenvalues): 211 energies.append(i.eigenvalues) 212 else: 213 energies.append(None) 214 return energies
Get a list with all lists of eigenvalues from all systems.
If no eigenvalues are present for a particular system, appends None.
217def get_gridsizes(systems:list) -> list: 218 """Get a list with all gridsize values. 219 220 If no gridsize value is present for a particular system, appends None. 221 """ 222 systems = as_list(systems) 223 gridsizes = [] 224 for i in systems: 225 if i.gridsize: 226 gridsizes.append(i.gridsize) 227 elif any(i.potential_values): 228 gridsizes.append(len(i.potential_values)) 229 else: 230 gridsizes.append(None) 231 return gridsizes
Get a list with all gridsize values.
If no gridsize value is present for a particular system, appends None.
234def get_runtimes(systems:list) -> list: 235 """Returns a list with all runtime values. 236 237 If no runtime value is present for a particular system, appends None. 238 """ 239 systems = as_list(systems) 240 runtimes = [] 241 for i in systems: 242 if i.runtime: 243 runtimes.append(i.runtime) 244 else: 245 runtimes.append(None) 246 return runtimes
Returns a list with all runtime values.
If no runtime value is present for a particular system, appends None.
294def calculate_ideal_E(E_level:int) -> int: 295 """Calculates the ideal energy for a specified `E_level`. 296 297 To be used in convergence tests with `potential_name = 'zero'`. 298 """ 299 real_E_level = None 300 if E_level % 2 == 0: 301 real_E_level = E_level / 2 302 else: 303 real_E_level = (E_level + 1) / 2 304 ideal_E = int(real_E_level ** 2) 305 return ideal_E
Calculates the ideal energy for a specified E_level.
To be used in convergence tests with potential_name = 'zero'.
308def sort_by_gridsize(systems:list) -> list: 309 """Sorts a list of System objects by `System.gridsize`.""" 310 systems = as_list(systems) 311 systems = sorted(systems, key=lambda sys: sys.gridsize) 312 return systems
Sorts a list of System objects by System.gridsize.
315def reduce_size(systems:list) -> list: 316 """Discard data that takes too much space. 317 318 Removes eigenvectors, potential values and grids, 319 for all System values inside the `systems` list. 320 """ 321 systems = as_list(systems) 322 for dataset in systems: 323 dataset = dataset.reduce_size() 324 return systems
Discard data that takes too much space.
Removes eigenvectors, potential values and grids,
for all System values inside the systems list.
327def summary( 328 systems, 329 verbose:bool=False 330 ) -> None: 331 """Print a summary of a System or list of Systems. 332 333 Print extra info with `verbose=True` 334 """ 335 print('--------------------') 336 systems = as_list(systems) 337 338 for system in systems: 339 dictionary = system.summary() 340 if verbose: 341 for key, value in dictionary.items(): 342 print(f'{key:<24}', value) 343 else: 344 eigenvalues = system.eigenvalues if any(system.eigenvalues) else [] 345 extra = '' 346 if len(system.eigenvalues) > 6: 347 eigenvalues = eigenvalues[:6] 348 extra = '...' 349 print('comment ' + str(system.comment)) 350 print('ZPE ' + str(system.eigenvalues[0])) 351 print('E activation ' + str(system.E_activation)) 352 print('V max ' + str(system.potential_max)) 353 print('1st splitting ' + str(system.splittings[0])) 354 print('1st excitation ' + str(system.excitations[0])) 355 print('B ' + str(system.B)) 356 print('eigenvalues ' + str([float(round(e, 4)) for e in eigenvalues]) + extra) 357 print('tags ' + str(system.tags)) 358 print('version ' + str(system.version)) 359 print('--------------------') 360 return None
Print a summary of a System or list of Systems.
Print extra info with verbose=True