fiqus 2024.5.2__py3-none-any.whl → 2024.7.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. fiqus/MainFiQuS.py +15 -5
  2. fiqus/data/DataConductor.py +301 -0
  3. fiqus/data/DataFiQuS.py +5 -2
  4. fiqus/data/DataFiQuSConductor.py +84 -0
  5. fiqus/data/DataFiQuSConductorAC_Strand.py +565 -0
  6. fiqus/data/DataFiQuSPancake3D.py +149 -39
  7. fiqus/data/RegionsModelFiQuS.py +4 -2
  8. fiqus/geom_generators/GeometryCCT.py +19 -17
  9. fiqus/geom_generators/GeometryConductorAC_Strand.py +1391 -0
  10. fiqus/getdp_runners/RunGetdpConductorAC_Strand.py +202 -0
  11. fiqus/getdp_runners/RunGetdpMultipole.py +4 -4
  12. fiqus/mains/MainConductorAC_Strand.py +133 -0
  13. fiqus/mesh_generators/MeshCCT.py +8 -8
  14. fiqus/mesh_generators/MeshConductorAC_Strand.py +657 -0
  15. fiqus/mesh_generators/MeshMultipole.py +11 -8
  16. fiqus/mesh_generators/MeshPancake3D.py +20 -18
  17. fiqus/plotters/PlotPythonConductorAC.py +855 -0
  18. fiqus/post_processors/PostProcessConductorAC.py +49 -0
  19. fiqus/pro_assemblers/ProAssembler.py +4 -3
  20. fiqus/pro_templates/combined/CCT_template.pro +25 -25
  21. fiqus/pro_templates/combined/ConductorAC_template.pro +1025 -0
  22. fiqus/pro_templates/combined/Multipole_template.pro +5 -5
  23. fiqus/pro_templates/combined/Pancake3D_template.pro +131 -46
  24. fiqus/pro_templates/combined/materials.pro +13 -9
  25. {fiqus-2024.5.2.dist-info → fiqus-2024.7.0.dist-info}/METADATA +2 -1
  26. {fiqus-2024.5.2.dist-info → fiqus-2024.7.0.dist-info}/RECORD +34 -22
  27. {fiqus-2024.5.2.dist-info → fiqus-2024.7.0.dist-info}/WHEEL +1 -1
  28. tests/test_geometry_generators.py +41 -0
  29. tests/test_mesh_generators.py +45 -0
  30. tests/test_solvers.py +52 -0
  31. tests/utils/fiqus_test_classes.py +42 -6
  32. tests/utils/generate_reference_files_ConductorAC.py +57 -0
  33. tests/utils/generate_reference_files_Pancake3D.py +92 -0
  34. {fiqus-2024.5.2.dist-info → fiqus-2024.7.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,202 @@
1
+ import timeit
2
+ import logging
3
+ import os
4
+ import subprocess
5
+ import re
6
+ import pandas as pd
7
+ import pickle
8
+ import numpy as np
9
+
10
+ import gmsh
11
+
12
+ from fiqus.utils.Utils import GmshUtils, FilesAndFolders
13
+ from fiqus.data.RegionsModelFiQuS import RegionsModel
14
+ import fiqus.data.DataFiQuSConductor as geom
15
+ from fiqus.geom_generators.GeometryConductorAC_Strand import TwistedStrand
16
+
17
+ from fiqus.pro_assemblers.ProAssembler import ASS_PRO
18
+
19
+ logger = logging.getLogger(__name__)
20
+
21
+ class Solve:
22
+ def __init__(self, fdm, GetDP_path, geometry_folder, mesh_folder, verbose=True):
23
+ self.fdm = fdm
24
+ self.cacdm = fdm.magnet
25
+ self.GetDP_path = GetDP_path
26
+ self.solution_folder = os.path.join(os.getcwd())
27
+ self.magnet_name = fdm.general.magnet_name
28
+ self.geometry_folder = geometry_folder
29
+ self.mesh_folder = mesh_folder
30
+ self.mesh_file = os.path.join(self.mesh_folder, f"{self.magnet_name}.msh")
31
+ self.pro_file = os.path.join(self.solution_folder, f"{self.magnet_name}.pro")
32
+ self.regions_file = os.path.join(mesh_folder, f"{self.magnet_name}.regions")
33
+
34
+ self.verbose = verbose
35
+ self.gu = GmshUtils(self.solution_folder, self.verbose)
36
+ self.gu.initialize()
37
+
38
+ self.ass_pro = ASS_PRO(os.path.join(self.solution_folder, self.magnet_name))
39
+ self.regions_model = FilesAndFolders.read_data_from_yaml(self.regions_file, RegionsModel)
40
+ self.material_properties_model = None
41
+
42
+ self.ed = {} # excitation dictionary
43
+
44
+ gmsh.option.setNumber("General.Terminal", verbose)
45
+
46
+ def read_excitation(self, inputs_folder_path):
47
+ """
48
+ Function for reading a CSV file for the 'from_file' excitation case.
49
+
50
+ :param inputs_folder_path: The full path to the folder with input files.
51
+ :type inputs_folder_path: str
52
+ """
53
+ if self.cacdm.solve.source_parameters.source_type == 'piecewise' and self.cacdm.solve.source_parameters.piecewise.source_csv_file:
54
+ input_file = os.path.join(inputs_folder_path, self.cacdm.solve.source_parameters.piecewise.source_csv_file)
55
+ print(f'Using excitation from file: {input_file}')
56
+ df = pd.read_csv(input_file, delimiter=',', engine='python')
57
+ excitation_time = df['time'].to_numpy(dtype='float').tolist()
58
+ self.ed['time'] = excitation_time
59
+ excitation_value = df['value'].to_numpy(dtype='float').tolist()
60
+ self.ed['value'] = excitation_value
61
+
62
+ def get_solution_parameters_from_yaml(self, inputs_folder_path):
63
+ """
64
+ Function for reading material properties from the geometry YAML file.
65
+
66
+ This reads the 'solution' section of the YAML file and stores it in the solution folder.
67
+ This could also be a place to change the material properties in the future.
68
+
69
+ :param inputs_folder_path: The full path to the folder with input files.
70
+ :type inputs_folder_path: str
71
+ """
72
+ if self.cacdm.geometry.io_settings.load.load_from_yaml:
73
+ #load the geometry class from the pkl file
74
+ geom_save_file = os.path.join(self.geometry_folder, f'{self.magnet_name}.pkl')
75
+ with open(geom_save_file, "rb") as geom_save_file:
76
+ geometry_class: TwistedStrand = pickle.load(geom_save_file)
77
+
78
+ input_yaml_file = os.path.join(inputs_folder_path, self.cacdm.geometry.io_settings.load.filename)
79
+ Conductor_dm = FilesAndFolders.read_data_from_yaml(input_yaml_file, geom.Conductor)
80
+ solution_parameters = Conductor_dm.Solution
81
+
82
+ # The geometry YAML file lists surfaces to exclude from the TI problem by their IDs. Here we convert these IDs to Gmsh physical surface tags.
83
+ # This is done by comparing the outer boundary points of the surfaces to exclude with the outer boundary points of the matrix partitions.
84
+ surfaces_excluded_from_TI_tags = []
85
+
86
+ for surface_ID in solution_parameters.Surfaces_excluded_from_TI: # 1) Find the outer boundary points of the surfaces to exclude
87
+ outer_boundary_points_a = []
88
+ outer_boundary_curves_a = Conductor_dm.Geometry.Areas[surface_ID].Boundary
89
+ for curve_ID in outer_boundary_curves_a:
90
+ curve = Conductor_dm.Geometry.Curves[curve_ID]
91
+ for point_ID in curve.Points:
92
+ point = Conductor_dm.Geometry.Points[point_ID]
93
+ outer_boundary_points_a.append(tuple(point.Coordinates))
94
+
95
+ for matrix_partition in geometry_class.matrix: # 2) Find the outer boundary points of the matrix partitions
96
+ outer_boundary_points_b = []
97
+ outer_boundary_curves_b = matrix_partition.boundary_curves
98
+ if len(outer_boundary_curves_b) == len(outer_boundary_curves_a): # If the number of boundary curves is different, the surfaces are not the same
99
+ for curve in outer_boundary_curves_b:
100
+ for point in curve.points:
101
+ outer_boundary_points_b.append(tuple(point.pos))
102
+
103
+ if np.allclose(sorted(outer_boundary_points_a), sorted(outer_boundary_points_b)): # If the outer boundary points are the same, the surfaces are the same
104
+ surfaces_excluded_from_TI_tags.append(matrix_partition.physical_surface_tag) # 3) Add the physical surface tag to the list of surfaces to exclude
105
+ break
106
+
107
+ solution_parameters.Surfaces_excluded_from_TI = surfaces_excluded_from_TI_tags # Replace the surface IDs with the physical surface tags
108
+
109
+ FilesAndFolders.write_data_to_yaml(os.path.join(self.solution_folder, "MaterialProperties.yaml"), solution_parameters.dict())
110
+ self.material_properties_model = solution_parameters
111
+
112
+
113
+
114
+
115
+ def assemble_pro(self):
116
+ print("Assembling .pro file")
117
+ self.ass_pro.assemble_combined_pro(template = self.cacdm.solve.pro_template, rm = self.regions_model, dm = self.fdm, ed=self.ed, mp=self.material_properties_model)
118
+
119
+ def run_getdp(self, solve = True, postOperation = True, gui = False):
120
+
121
+ command = ["-v2", "-verbose", "3"]
122
+ if solve:
123
+ command += ["-solve", "MagDyn"]
124
+ if self.cacdm.solve.formulation_parameters.dynamic_correction:
125
+ command += ["-pos", "MagDyn", "MagDyn_dynCorr"]
126
+ else:
127
+ command += ["-pos", "MagDyn"]
128
+
129
+
130
+ startTime = timeit.default_timer()
131
+ getdpProcess = subprocess.Popen([self.GetDP_path, self.pro_file, "-msh", self.mesh_file] + command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
132
+
133
+ with getdpProcess.stdout:
134
+ for line in iter(getdpProcess.stdout.readline, b""):
135
+ line = line.decode("utf-8").rstrip()
136
+ line = line.split("\r")[-1]
137
+ if not "Test" in line:
138
+ if line.startswith("Info"):
139
+ parsedLine = re.sub(r"Info\s+:\s+", "", line)
140
+ logger.info(parsedLine)
141
+ elif line.startswith("Warning"):
142
+ parsedLine = re.sub(r"Warning\s+:\s+", "", line)
143
+ logger.warning(parsedLine)
144
+ elif line.startswith("Error"):
145
+ parsedLine = re.sub(r"Error\s+:\s+", "", line)
146
+ logger.error(parsedLine)
147
+ logger.error("Solving CAC failed.")
148
+ # raise Exception(parsedLine)
149
+ elif re.match("##", line):
150
+ logger.critical(line)
151
+ else:
152
+ logger.info(line)
153
+
154
+ simulation_time = timeit.default_timer()-startTime
155
+ # Save simulation time:
156
+ if solve:
157
+ logger.info(f"Solving CAC_1 has finished in {round(simulation_time, 3)} seconds.")
158
+ with open(os.path.join(self.solution_folder, 'test_temporary', 'simulation_time.txt'), 'w') as file:
159
+ file.write(str(simulation_time))
160
+
161
+
162
+ if gui and ((postOperation and not solve) or (solve and postOperation and self.cacdm.postproc.generate_pos_files)):
163
+ # gmsh.option.setNumber("Geometry.Volumes", 1)
164
+ # gmsh.option.setNumber("Geometry.Surfaces", 1)
165
+ # gmsh.option.setNumber("Geometry.Curves", 1)
166
+ # gmsh.option.setNumber("Geometry.Points", 0)
167
+ posFiles = [
168
+ fileName
169
+ for fileName in os.listdir(self.solution_folder)
170
+ if fileName.endswith(".pos")
171
+ ]
172
+ for posFile in posFiles:
173
+ gmsh.open(os.path.join(self.solution_folder, posFile))
174
+ self.gu.launch_interactive_GUI()
175
+ else:
176
+ gmsh.clear()
177
+ gmsh.finalize()
178
+
179
+ def cleanup(self):
180
+ """
181
+ This funtion is used to remove .msh, .pre and .res files from the solution folder, as they may be large and not needed.
182
+ """
183
+ magnet_name = self.fdm.general.magnet_name
184
+ cleanup = self.cacdm.postproc.cleanup
185
+
186
+ if cleanup.remove_res_file:
187
+ res_file_path = os.path.join(self.solution_folder, f"{magnet_name}.res")
188
+ if os.path.exists(res_file_path):
189
+ os.remove(res_file_path)
190
+ logger.info(f"Removed {magnet_name}.res")
191
+
192
+ if cleanup.remove_pre_file:
193
+ pre_file_path = os.path.join(self.solution_folder, f"{magnet_name}.pre")
194
+ if os.path.exists(pre_file_path):
195
+ os.remove(pre_file_path)
196
+ logger.info(f"Removed {magnet_name}.pre")
197
+
198
+ if cleanup.remove_msh_file:
199
+ msh_file_path = os.path.join(self.mesh_folder, f"{magnet_name}.msh")
200
+ if os.path.exists(msh_file_path):
201
+ os.remove(msh_file_path)
202
+ logger.info(f"Removed {magnet_name}.msh")
@@ -59,12 +59,12 @@ class RunGetdpMultipole:
59
59
  self.rm = Util.read_data_from_yaml(f"{os.path.join(self.mesh_folder, self.data.general.magnet_name)}.reg", rM)
60
60
 
61
61
  def assemblePro(self):
62
- self.rm.powered.vol.currents = []
63
- for name in self.rm.powered.vol.names:
62
+ self.rm.powered['Multipole'].vol.currents = []
63
+ for name in self.rm.powered['Multipole'].vol.names:
64
64
  if name[-3:] == 'pos':
65
- self.rm.powered.vol.currents.append(self.II)
65
+ self.rm.powered['Multipole'].vol.currents.append(self.II)
66
66
  else:
67
- self.rm.powered.vol.currents.append(-self.II)
67
+ self.rm.powered['Multipole'].vol.currents.append(-self.II)
68
68
 
69
69
  ap = aP(file_base_path=os.path.join(self.solution_folder, self.data.general.magnet_name),
70
70
  naming_conv=self.naming_conv)
@@ -0,0 +1,133 @@
1
+ import os
2
+
3
+ from fiqus.geom_generators.GeometryConductorAC_Strand import Geometry
4
+ from fiqus.mesh_generators.MeshConductorAC_Strand import Mesh, StrandMesh
5
+ from fiqus.getdp_runners.RunGetdpConductorAC_Strand import Solve
6
+ from fiqus.post_processors.PostProcessConductorAC import PostProcess
7
+ from fiqus.plotters.PlotPythonConductorAC import PlotPython
8
+
9
+ class MainConductorAC_Strand:
10
+ def __init__(self, fdm, inputs_folder_path='', outputs_folder_path='', verbose=True):
11
+ """
12
+ Main class for working with simulations for the Conductor AC model.
13
+ :param fdm: FiQuS data model
14
+ :param inputs_folder_path: full path to folder with input files
15
+ :param verbose: if True, more info is printed in the console
16
+ """
17
+ self.verbose = verbose
18
+ self.fdm = fdm
19
+ self.inputs_folder_path = inputs_folder_path
20
+ self.outputs_folder_path = outputs_folder_path
21
+ self.GetDP_path = None
22
+ self.geom_folder = None
23
+ self.mesh_folder = None
24
+ self.solution_folder = None
25
+ self.model_file = None
26
+ self.model_folder = None
27
+
28
+
29
+ def generate_geometry(self, gui=False):
30
+ """
31
+ Generates the strand geometry.
32
+ """
33
+ os.chdir(self.geom_folder)
34
+ g = Geometry(fdm=self.fdm, inputs_folder_path=self.inputs_folder_path, verbose=self.verbose)
35
+ g.generate_strand_geometry(gui)
36
+
37
+
38
+ def load_geometry(self, gui: bool = False):
39
+ """
40
+ Loads the previously generated geometry from the .brep file.
41
+ """
42
+ os.chdir(self.geom_folder)
43
+ g = Geometry(fdm=self.fdm, inputs_folder_path=self.inputs_folder_path, verbose=self.verbose)
44
+ g.load_conductor_geometry(gui)
45
+ # self.model_file = g.model_file
46
+
47
+ def pre_process(self, gui=False):
48
+ pass
49
+
50
+ def mesh(self, gui: bool = False):
51
+ """
52
+ Generates the mesh for the strand geometry.
53
+ """
54
+ os.chdir(self.mesh_folder)
55
+
56
+ m = StrandMesh(fdm=self.fdm, verbose=self.verbose)
57
+ m.generate_mesh(self.geom_folder)
58
+ m.generate_cuts()
59
+ m.generate_regions_file()
60
+ m.save_mesh(gui)
61
+
62
+ return {"test": 0}
63
+
64
+ def load_mesh(self, gui=False):
65
+ """
66
+ Loads the previously generated mesh from the MSH file.
67
+ """
68
+ os.chdir(self.mesh_folder)
69
+ m = Mesh(fdm=self.fdm, verbose=self.verbose)
70
+ m.load_mesh(gui)
71
+
72
+ # self.model_file = m.mesh_file
73
+
74
+ def solve_and_postprocess_getdp(self, gui: bool = False):
75
+ """
76
+ Assembles the .pro-file from the template, then runs the simulation and the post-processing steps using GetDP.
77
+ """
78
+ os.chdir(self.solution_folder)
79
+
80
+ s = Solve(self.fdm, self.GetDP_path, self.geom_folder, self.mesh_folder, self.verbose)
81
+ s.read_excitation(inputs_folder_path=self.inputs_folder_path)
82
+ s.get_solution_parameters_from_yaml(inputs_folder_path=self.inputs_folder_path)
83
+ s.assemble_pro()
84
+ s.run_getdp(solve = True, postOperation = True, gui = gui)
85
+ s.cleanup()
86
+
87
+ # def pre_process(self):
88
+ # os.chdir(self.solution_folder)
89
+
90
+ # s = Solve(self.fdm, self.GetDP_path, self.mesh_folder, self.verbose)
91
+
92
+ def post_process_getdp(self, gui: bool = False):
93
+ """
94
+ Runs the post-processing steps trough GetDP.
95
+ """
96
+ os.chdir(self.solution_folder)
97
+
98
+ s = Solve(self.fdm, self.GetDP_path, self.geom_folder, self.mesh_folder, self.verbose)
99
+ s.read_excitation(inputs_folder_path=self.inputs_folder_path)
100
+ s.assemble_pro()
101
+ s.run_getdp(solve = False, postOperation = True, gui = gui)
102
+ #
103
+ def post_process_python(self, gui: bool = False):
104
+ # os.chdir(self.solution_folder)
105
+ postProc = PostProcess(self.fdm, self.outputs_folder_path)
106
+ postProc.plot_instantaneous_loss()
107
+
108
+ return {'test': 0}
109
+
110
+ def batch_post_process_python(self, gui: bool = False):
111
+ """
112
+ Runs batch post-processing steps using Python.
113
+ Used for gathering, analysing, comparing and plotting data from multiple simulations.
114
+ """
115
+ plotter = PlotPython(self.fdm, csv_filename=self.fdm.magnet.postproc.batch_postproc.postProc_csv, lossMap_gridData_folder=None, inputs_folder_path=self.inputs_folder_path, outputs_folder_path=self.outputs_folder_path)
116
+
117
+ if self.fdm.magnet.postproc.batch_postproc.loss_map.produce_loss_map:
118
+ # plotter.save_lossMap_gridData()
119
+ # plotter.save_magnetization()
120
+ plotter.create_lossMap()
121
+
122
+ if self.fdm.magnet.postproc.batch_postproc.loss_map.cross_section.plot_cross_section:
123
+ plotter.plot_lossMap_crossSection()
124
+
125
+ if self.fdm.magnet.postproc.batch_postproc.loss_map.cross_section_sweep.animate_cross_section_sweep:
126
+ plotter.animate_lossMap_crossSection()
127
+
128
+ if self.fdm.magnet.postproc.batch_postproc.plot2d.produce_plot2d:
129
+ plotter.plot2d()
130
+
131
+
132
+ # def plot_python(self):
133
+ # pass
@@ -52,7 +52,7 @@ class Mesh:
52
52
  r_types = ['w' for _ in self.cctwi.w_names] + ['f' for _ in self.cctwi.f_names] # needed for picking different pow_surf later on
53
53
  vol_max_loop = 0
54
54
 
55
- for i, (f_name, r_type, r_name, r_tag) in enumerate(zip(self.cctwi.w_names + self.cctwi.f_names, r_types, self.cctrm.powered.vol.names, self.cctrm.powered.vol.numbers)):
55
+ for i, (f_name, r_type, r_name, r_tag) in enumerate(zip(self.cctwi.w_names + self.cctwi.f_names, r_types, self.cctrm.powered['cct'].vol.names, self.cctrm.powered['cct'].vol.numbers)):
56
56
  vol_dict = json.load(open(f"{os.path.join(self.geom_folder, f_name)}.vi"))
57
57
  volumes_file = np.array(vol_dict['all'])
58
58
  vol_max_file = np.max(volumes_file)
@@ -63,15 +63,15 @@ class Mesh:
63
63
  gmsh.model.addPhysicalGroup(dim=3, tags=v_tags, tag=r_tag)
64
64
  gmsh.model.setPhysicalName(dim=3, tag=r_tag, name=r_name)
65
65
  powered_in_surf = self._find_surf(v_tags[0])
66
- gmsh.model.addPhysicalGroup(dim=2, tags=[powered_in_surf], tag=self.cctrm.powered.surf_in.numbers[i])
67
- gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.powered.surf_in.numbers[i], name=self.cctrm.powered.surf_in.names[i])
66
+ gmsh.model.addPhysicalGroup(dim=2, tags=[powered_in_surf], tag=self.cctrm.powered['cct'].surf_in.numbers[i])
67
+ gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.powered['cct'].surf_in.numbers[i], name=self.cctrm.powered['cct'].surf_in.names[i])
68
68
  powered_out_surf = self._find_surf(v_tags[-1])
69
- gmsh.model.addPhysicalGroup(dim=2, tags=[powered_out_surf], tag=self.cctrm.powered.surf_out.numbers[i])
70
- gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.powered.surf_out.numbers[i], name=self.cctrm.powered.surf_out.names[i])
69
+ gmsh.model.addPhysicalGroup(dim=2, tags=[powered_out_surf], tag=self.cctrm.powered['cct'].surf_out.numbers[i])
70
+ gmsh.model.setPhysicalName(dim=2, tag=self.cctrm.powered['cct'].surf_out.numbers[i], name=self.cctrm.powered['cct'].surf_out.names[i])
71
71
 
72
72
  vol_max_loop = v_tags[-1]
73
73
 
74
- for r_name, r_tag in zip(self.cctrm.induced.vol.names, self.cctrm.induced.vol.numbers):
74
+ for r_name, r_tag in zip(self.cctrm.induced['cct'].vol.names, self.cctrm.induced['cct'].vol.numbers):
75
75
  vol_max_loop += 1
76
76
  self.formers.append(vol_max_loop)
77
77
  gmsh.model.addPhysicalGroup(dim=3, tags=[vol_max_loop], tag=r_tag)
@@ -180,9 +180,9 @@ class Mesh:
180
180
  if self.verbose:
181
181
  print('Generating Cuts Started')
182
182
  start_time = timeit.default_timer()
183
- for vol, surf_in, surf_out in zip(self.cctrm.powered.vol.numbers, self.cctrm.powered.surf_in.numbers, self.cctrm.powered.surf_out.numbers):
183
+ for vol, surf_in, surf_out in zip(self.cctrm.powered['cct'].vol.numbers, self.cctrm.powered['cct'].surf_in.numbers, self.cctrm.powered['cct'].surf_out.numbers):
184
184
  gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[vol], subdomainTags=[surf_in, surf_out], dims=[1, 2, 3])
185
- for vol in self.cctrm.induced.vol.numbers:
185
+ for vol in self.cctrm.induced['cct'].vol.numbers:
186
186
  gmsh.model.mesh.addHomologyRequest("Cohomology", domainTags=[vol], dims=[1, 2, 3])
187
187
  gmsh.model.mesh.computeHomology()
188
188
  if self.verbose: