emerge 0.6.8__py3-none-any.whl → 0.6.10__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.

Potentially problematic release.


This version of emerge might be problematic. Click here for more details.

@@ -284,15 +284,16 @@ class PVDisplay(BaseDisplay):
284
284
  self._ruler.min_length = max(1e-3, min(self._mesh.edge_lengths))
285
285
  self._update_camera()
286
286
  self._add_aux_items()
287
+ # self._plot.renderer.enable_depth_peeling(20, 0.8)
288
+ # self._plot.enable_anti_aliasing(self.set.anti_aliassing)
287
289
  if self._do_animate:
288
290
  self._wire_close_events()
289
291
  self.add_text('Press Q to close!',color='red', position='upper_left')
290
292
  self._plot.show(auto_close=False, interactive_update=True, before_close_callback=self._close_callback)
291
293
  self._animate()
292
-
293
-
294
294
  else:
295
295
  self._plot.show()
296
+
296
297
  self._reset()
297
298
 
298
299
  def set_mesh(self, mesh: Mesh3D):
@@ -440,8 +441,20 @@ class PVDisplay(BaseDisplay):
440
441
  opacity = obj.opacity
441
442
  line_width = 0.5
442
443
  color = obj.color_rgb
444
+ metal = obj._metal
443
445
  style='surface'
444
446
 
447
+ # Default render settings
448
+ metallic = 0.05
449
+ roughness = 0.5
450
+ pbr = False
451
+
452
+ if metal:
453
+ pbr = True
454
+ metallic = 0.8
455
+ roughness = 0.3
456
+
457
+ # Default keyword arguments when plotting Mesh mode.
445
458
  if mesh is True:
446
459
  show_edges = True
447
460
  opacity = 0.7
@@ -449,13 +462,28 @@ class PVDisplay(BaseDisplay):
449
462
  style='wireframe'
450
463
  color=next(C_CYCLE)
451
464
 
452
- kwargs = setdefault(kwargs, color=color, opacity=opacity, line_width=line_width, show_edges=show_edges, pickable=True, style=style)
465
+ # Defining the default keyword arguments for PyVista
466
+ kwargs = setdefault(kwargs, color=color,
467
+ opacity=opacity,
468
+ metallic=metallic,
469
+ pbr=pbr,
470
+ roughness=roughness,
471
+ line_width=line_width,
472
+ show_edges=show_edges,
473
+ pickable=True,
474
+ style=style)
453
475
  mesh_obj = self.mesh(obj)
454
476
 
455
477
  if mesh is True and volume_mesh is True:
456
478
  mesh_obj = mesh_obj.extract_all_edges()
457
-
458
479
  actor = self._plot.add_mesh(mesh_obj, *args, **kwargs)
480
+
481
+ # Push 3D Geometries back to avoid Z-fighting with 2D geometries.
482
+ if obj.dim==3:
483
+ mapper = actor.GetMapper()
484
+ mapper.SetResolveCoincidentTopology(1)
485
+ mapper.SetRelativeCoincidentTopologyPolygonOffsetParameters(1,1)
486
+
459
487
  self._plot.add_mesh(self._volume_edges(_select(obj)), color='#000000', line_width=2, show_edges=True)
460
488
 
461
489
  def add_scatter(self, xs: np.ndarray, ys: np.ndarray, zs: np.ndarray):
@@ -568,7 +596,7 @@ class PVDisplay(BaseDisplay):
568
596
 
569
597
 
570
598
  if scale=='log':
571
- T = lambda x: np.log10(np.abs(x))
599
+ T = lambda x: np.log10(np.abs(x+1e-12))
572
600
  elif scale=='symlog':
573
601
  T = lambda x: np.sign(x) * np.log10(1 + np.abs(x*np.log(10)))
574
602
  else:
@@ -1,3 +1,4 @@
1
+ from typing import Literal
1
2
 
2
3
  class PVDisplaySettings:
3
4
 
@@ -22,4 +23,6 @@ class PVDisplaySettings:
22
23
  self.background_bottom: str = "#c0d2e8"
23
24
  self.background_top: str = "#ffffff"
24
25
  self.grid_line_color: str = "#8e8e8e"
25
- self.z_boost: float = 1e-6
26
+ self.z_boost: float = 0#1e-9
27
+ self.depth_peeling: bool = True
28
+ self.anti_aliassing: Literal["msaa","ssaa",'fxaa'] = "msaa"
@@ -406,25 +406,30 @@ and sparse frequency annotations (e.g., labeled by frequency).
406
406
  plt.tight_layout()
407
407
  plt.show()
408
408
 
409
- def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
410
- dblim=[-40, 5],
411
- xunit="GHz",
412
- levelindicator: int | float | None = None,
413
- noise_floor=-150,
414
- fill_areas: list[tuple] | None = None,
415
- spec_area: list[tuple[float,...]] | None = None,
416
- unwrap_phase=False,
417
- logx: bool = False,
418
- labels: list[str] | None = None,
419
- linestyles: list[str] | None = None,
420
- colorcycle: list[int] | None = None,
421
- filename: str | None = None,
422
- show_plot: bool = True,
423
- figdata: tuple | None = None) -> tuple[plt.Figure, plt.Axes, plt.Axes]:
409
+ def plot_sp(f: np.ndarray | list[np.ndarray], S: list[np.ndarray] | np.ndarray,
410
+ dblim=[-40, 5],
411
+ xunit="GHz",
412
+ levelindicator: int | float | None = None,
413
+ noise_floor=-150,
414
+ fill_areas: list[tuple] | None = None,
415
+ spec_area: list[tuple[float,...]] | None = None,
416
+ unwrap_phase=False,
417
+ logx: bool = False,
418
+ labels: list[str] | None = None,
419
+ linestyles: list[str] | None = None,
420
+ colorcycle: list[int] | None = None,
421
+ filename: str | None = None,
422
+ show_plot: bool = True,
423
+ figdata: tuple | None = None) -> tuple[plt.Figure, plt.Axes, plt.Axes]:
424
424
  """Plot S-parameters in dB and phase
425
+
426
+ One may provide:
427
+ - A single frequency with a single S-parameter
428
+ - A single frequency with a list of S-parameters
429
+ - A list of frequencies with a list of S-parameters
425
430
 
426
431
  Args:
427
- f (np.ndarray): Frequency vector
432
+ f (np.ndarray | list[np.ndarray]): Frequency vector or list of frequencies
428
433
  S (list[np.ndarray] | np.ndarray): S-parameters to plot (list or single array)
429
434
  dblim (list, optional): Decibel y-axis limit. Defaults to [-80, 5].
430
435
  xunit (str, optional): Frequency unit. Defaults to "GHz".
@@ -444,7 +449,12 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
444
449
  Ss = [S]
445
450
  else:
446
451
  Ss = S
447
-
452
+
453
+ if not isinstance(f, list):
454
+ fs = [f for _ in Ss]
455
+ else:
456
+ fs = f
457
+
448
458
  if linestyles is None:
449
459
  linestyles = ['-' for _ in S]
450
460
 
@@ -452,7 +462,8 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
452
462
  colorcycle = [i for i, S in enumerate(S)]
453
463
 
454
464
  unitdivider: dict[str, float] = {"MHz": 1e6, "GHz": 1e9, "kHz": 1e3}
455
- fnew = f / unitdivider[xunit]
465
+
466
+ fs = [f / unitdivider[xunit] for f in fs]
456
467
 
457
468
  if figdata is None:
458
469
  # Create two subplots: one for magnitude and one for phase
@@ -463,10 +474,10 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
463
474
  minphase, maxphase = -180, 180
464
475
 
465
476
  maxy = 0
466
- for s, ls, cid in zip(Ss, linestyles, colorcycle):
477
+ for f, s, ls, cid in zip(fs, Ss, linestyles, colorcycle):
467
478
  # Calculate and plot magnitude in dB
468
479
  SdB = 20 * np.log10(np.abs(s) + 10**(noise_floor/20) * np.random.rand(*s.shape) + 10**((noise_floor-30)/20))
469
- ax_mag.plot(fnew, SdB, label="Magnitude (dB)", linestyle=ls, color=EMERGE_COLORS[cid % len(EMERGE_COLORS)])
480
+ ax_mag.plot(f, SdB, label="Magnitude (dB)", linestyle=ls, color=EMERGE_COLORS[cid % len(EMERGE_COLORS)])
470
481
  if np.max(SdB) > maxy:
471
482
  maxy = np.max(SdB)
472
483
  # Calculate and plot phase in degrees
@@ -475,12 +486,12 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
475
486
  phase = np.unwrap(phase, period=360)
476
487
  minphase = min(np.min(phase), minphase)
477
488
  maxphase = max(np.max(phase), maxphase)
478
- ax_phase.plot(fnew, phase, label="Phase (degrees)", linestyle=ls, color=EMERGE_COLORS[cid % len(EMERGE_COLORS)])
489
+ ax_phase.plot(f, phase, label="Phase (degrees)", linestyle=ls, color=EMERGE_COLORS[cid % len(EMERGE_COLORS)])
479
490
 
480
491
  # Annotate level indicators if specified
481
492
  if isinstance(levelindicator, (int, float)) and levelindicator is not None:
482
493
  lvl = levelindicator
483
- fcross = hintersections(fnew, SdB, lvl)
494
+ fcross = hintersections(f, SdB, lvl)
484
495
  for fs in fcross:
485
496
  ax_mag.annotate(
486
497
  f"{str(fs)[:4]}{xunit}",
@@ -500,16 +511,18 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
500
511
  f2 = fmax / unitdivider[xunit]
501
512
  ax_mag.fill_between([f1, f2], vmin,vmax, color='red', alpha=0.2)
502
513
  # Configure magnitude plot (ax_mag)
514
+ fmin = min([min(f) for f in fs])
515
+ fmax = max([max(f) for f in fs])
503
516
  ax_mag.set_ylabel("Magnitude (dB)")
504
517
  ax_mag.set_xlabel(f"Frequency ({xunit})")
505
- ax_mag.axis([min(fnew), max(fnew), dblim[0], max(maxy*1.1,dblim[1])]) # type: ignore
518
+ ax_mag.axis([fmin, fmax, dblim[0], max(maxy*1.1,dblim[1])]) # type: ignore
506
519
  ax_mag.axhline(y=0, color="k", linewidth=1)
507
520
  ax_mag.xaxis.set_minor_locator(tck.AutoMinorLocator(2))
508
521
  ax_mag.yaxis.set_minor_locator(tck.AutoMinorLocator(2))
509
522
  # Configure phase plot (ax_phase)
510
523
  ax_phase.set_ylabel("Phase (degrees)")
511
524
  ax_phase.set_xlabel(f"Frequency ({xunit})")
512
- ax_phase.axis([min(fnew), max(fnew), minphase, maxphase]) # type: ignore
525
+ ax_phase.axis([fmin, fmax, minphase, maxphase]) # type: ignore
513
526
  ax_phase.xaxis.set_minor_locator(tck.AutoMinorLocator(2))
514
527
  ax_phase.yaxis.set_minor_locator(tck.AutoMinorLocator(2))
515
528
  if logx:
@@ -527,7 +540,7 @@ def plot_sp(f: np.ndarray, S: list[np.ndarray] | np.ndarray,
527
540
 
528
541
 
529
542
  def plot_ff(
530
- theta: np.ndarray,
543
+ theta: np.ndarray | list[np.ndarray],
531
544
  E: Union[np.ndarray, Sequence[np.ndarray]],
532
545
  grid: bool = True,
533
546
  dB: bool = False,
@@ -546,7 +559,7 @@ def plot_ff(
546
559
 
547
560
  Parameters
548
561
  ----------
549
- theta : np.ndarray
562
+ theta : np.ndarray | list[np.ndarray]
550
563
  Angle array (radians).
551
564
  E : np.ndarray or sequence of np.ndarray
552
565
  Complex E-field samples; magnitude will be plotted.
@@ -567,6 +580,12 @@ def plot_ff(
567
580
  E_list = [E]
568
581
  else:
569
582
  E_list = list(E)
583
+
584
+ if not isinstance(theta, list):
585
+ thetas = [theta for _ in E_list]
586
+ else:
587
+ thetas = theta
588
+
570
589
  n_series = len(E_list)
571
590
 
572
591
  # Style broadcasting
@@ -583,6 +602,7 @@ def plot_ff(
583
602
 
584
603
  fig, ax = plt.subplots()
585
604
  for i, Ei in enumerate(E_list):
605
+ theta = thetas[i]
586
606
  mag = np.abs(Ei)
587
607
  if dB:
588
608
  mag = 20*np.log10(mag)
@@ -612,6 +632,8 @@ def plot_ff(
612
632
  def plot_ff_polar(
613
633
  theta: np.ndarray,
614
634
  E: Union[np.ndarray, Sequence[np.ndarray]],
635
+ dB: bool = False,
636
+ dBfloor: float = -30,
615
637
  labels: Optional[List[str]] = None,
616
638
  linestyles: Union[str, List[str]] = "-",
617
639
  linewidth: float = 2.0,
@@ -649,6 +671,8 @@ def plot_ff_polar(
649
671
  E_list = list(E)
650
672
  n_series = len(E_list)
651
673
 
674
+ if dB:
675
+ E_list = [20*np.log10(np.clip(np.abs(e), a_min=10**(dBfloor/20), a_max = 1e9)) for e in E_list]
652
676
  # Style broadcasting
653
677
  def _broadcast(param, default):
654
678
  if isinstance(param, list):
@@ -665,11 +689,15 @@ def plot_ff_polar(
665
689
  ax.set_theta_zero_location(zero_location) # type: ignore
666
690
  ax.set_theta_direction(-1 if clockwise else 1) # type: ignore
667
691
  ax.set_rlabel_position(rlabel_angle) # type: ignore
692
+ ymin = min([min(E) for E in E_list])
693
+ ymax = max([max(E) for E in E_list])
694
+ yrange = ymax-ymin
668
695
 
696
+ ax.set_ylim(ymin-0.05*yrange, ymax+0.05*yrange)
669
697
  for i, Ei in enumerate(E_list):
670
- mag = np.abs(Ei)
698
+
671
699
  ax.plot(
672
- theta, mag,
700
+ theta, Ei,
673
701
  linestyle=linestyles[i],
674
702
  linewidth=linewidth,
675
703
  marker=markers[i],
@@ -17,8 +17,7 @@ with em.Simulation("myfile", load_file=True) as m:
17
17
  S11 = data.S(1,1)
18
18
  S21 = data.S(2,1)
19
19
  plt.plot_sp(f/1e9, [S11, S21])
20
-
21
- m.set_mesh(m.data.mw.field[0].mesh)
20
+
22
21
  m.display.add_object(m['box'])
23
22
  m.display.add_surf(*m.data.mw.field[0].cutplane(1*mm, z=5*mm).scalar('Ez','real'))
24
23
  m.display.show()
@@ -205,6 +205,10 @@ class Selection:
205
205
  def centers(self) -> list[tuple[float, float, float],]:
206
206
  return [gmsh.model.occ.get_center_of_mass(self.dim, tag) for tag in self.tags]
207
207
 
208
+ @property
209
+ def _metal(self) -> bool:
210
+ return False
211
+
208
212
  @property
209
213
  def opacity(self) -> float:
210
214
  return 0.6
@@ -31,7 +31,7 @@ from typing import Literal, Generator, Any
31
31
  from loguru import logger
32
32
  import numpy as np
33
33
  import gmsh # type: ignore
34
- import joblib # type: ignore
34
+ import cloudpickle
35
35
  import os
36
36
  import inspect
37
37
  from pathlib import Path
@@ -68,7 +68,7 @@ class Simulation:
68
68
  loglevel: Literal['TRACE','DEBUG','INFO','WARNING','ERROR'] = 'INFO',
69
69
  load_file: bool = False,
70
70
  save_file: bool = False,
71
- logfile: bool = False,
71
+ write_log: bool = False,
72
72
  path_suffix: str = ".EMResults"):
73
73
  """Generate a Simulation class object.
74
74
 
@@ -80,7 +80,7 @@ class Simulation:
80
80
  loglevel ("DEBUG","INFO","WARNING","ERROR", optional): The loglevel to use for loguru. Defaults to 'INFO'.
81
81
  load_file (bool, optional): If the simulatio model should be loaded from a file. Defaults to False.
82
82
  save_file (bool, optional): if the simulation file should be stored to a file. Defaults to False.
83
- logfile (bool, optional): If a file should be created that contains the entire log of the simulation. Defaults to False.
83
+ write_log (bool, optional): If a file should be created that contains the entire log of the simulation. Defaults to False.
84
84
  path_suffix (str, optional): The suffix that will be added to the results directory. Defaults to ".EMResults".
85
85
  """
86
86
 
@@ -113,9 +113,10 @@ class Simulation:
113
113
  self._initialize_simulation()
114
114
 
115
115
  self.set_loglevel(loglevel)
116
- if logfile:
117
- self.set_logfile()
116
+ if write_log:
117
+ self.set_write_log()
118
118
 
119
+ LOG_CONTROLLER._flush_log_buffer()
119
120
  self._update_data()
120
121
 
121
122
 
@@ -181,7 +182,10 @@ class Simulation:
181
182
 
182
183
  # Restier the Exit GMSH function on proper program abortion
183
184
  register(self._exit_gmsh)
184
-
185
+ else:
186
+ gmsh.finalize()
187
+ gmsh.initialize()
188
+
185
189
  # Create a new GMSH model or load it
186
190
  if not self.load_file:
187
191
  gmsh.model.add(self.modelname)
@@ -283,7 +287,8 @@ class Simulation:
283
287
  # Pack and save data
284
288
  dataset = dict(simdata=self.data, mesh=self.mesh)
285
289
  data_path = self.modelpath / 'simdata.emerge'
286
- joblib.dump(dataset, str(data_path))
290
+ with open(str(data_path), "wb") as f_out:
291
+ cloudpickle.dump(dataset, f_out)
287
292
  logger.info(f"Saved simulation data to: {data_path}")
288
293
 
289
294
  def load(self) -> None:
@@ -304,7 +309,8 @@ class Simulation:
304
309
  #self.mesh.update([])
305
310
 
306
311
  # Load data
307
- datapack = joblib.load(str(data_path))
312
+ with open(str(data_path), "rb") as f_in:
313
+ datapack= cloudpickle.load(f_in)
308
314
  self.data = datapack['simdata']
309
315
  self._set_mesh(datapack['mesh'])
310
316
  logger.info(f"Loaded simulation data from: {data_path}")
@@ -319,7 +325,7 @@ class Simulation:
319
325
  if loglevel not in ('TRACE','DEBUG'):
320
326
  gmsh.option.setNumber("General.Terminal", 0)
321
327
 
322
- def set_logfile(self) -> None:
328
+ def set_write_log(self) -> None:
323
329
  """Adds a file output for the logger."""
324
330
  LOG_CONTROLLER.set_write_file(self.modelpath)
325
331
 
@@ -15,7 +15,16 @@
15
15
  # along with this program; if not, see
16
16
  # <https://www.gnu.org/licenses/>.
17
17
 
18
- import cupy as cp # ty: ignore
18
+ import warnings
19
+ from loguru import logger
20
+
21
+ # Catch the Cuda warning and print it with Loguru.
22
+ with warnings.catch_warnings(record=True) as w:
23
+ warnings.simplefilter("always")
24
+ import cupy as cp
25
+ for warn in w:
26
+ logger.debug(f"{warn.category.__name__}: {warn.message}")
27
+
19
28
  import nvmath.bindings.cudss as cudss # ty: ignore
20
29
  from nvmath import CudaDataType # ty: ignore
21
30
 
@@ -23,9 +32,6 @@ from scipy.sparse import csr_matrix
23
32
  import numpy as np
24
33
  from typing import Literal
25
34
 
26
- from loguru import logger
27
-
28
-
29
35
  ############################################################
30
36
  # CONSTANTS #
31
37
  ############################################################
emerge/_emerge/solver.py CHANGED
@@ -504,6 +504,7 @@ class SolverSuperLU(Solver):
504
504
  logger.info(f'[ID={id}] Calling SuperLU Solver.')
505
505
  self.single = True
506
506
  if not reuse_factorization:
507
+ logger.trace('Computing LU-Decomposition')
507
508
  self.lu = splu(A, permc_spec='MMD_AT_PLUS_A', relax=0, diag_pivot_thresh=self._pivoting_threshold, options=self.options)
508
509
  x = self.lu.solve(b)
509
510
  aux = {
@@ -518,6 +519,7 @@ class SolverUMFPACK(Solver):
518
519
 
519
520
  def __init__(self):
520
521
  super().__init__()
522
+ logger.trace('Creating UMFPACK solver')
521
523
  self.A: np.ndarray = None
522
524
  self.b: np.ndarray = None
523
525
 
@@ -532,6 +534,7 @@ class SolverUMFPACK(Solver):
532
534
  def initialize(self):
533
535
  if self.initalized:
534
536
  return
537
+ logger.trace('Initializing UMFPACK Solver')
535
538
  self.umfpack = um.UmfpackContext('zl')
536
539
  self.umfpack.control[um.UMFPACK_PRL] = 0 # ty: ignore
537
540
  self.umfpack.control[um.UMFPACK_IRSTEP] = 2 # ty: ignore
@@ -542,7 +545,9 @@ class SolverUMFPACK(Solver):
542
545
  self.umfpack.control[um.UMFPACK_BLOCK_SIZE] = 64 # ty: ignore
543
546
  self.umfpack.control[um.UMFPACK_FIXQ] = -1 # ty: ignore
544
547
  self.initalized = True
548
+
545
549
  def reset(self) -> None:
550
+ logger.trace('Resetting UMFPACK solver state')
546
551
  self.fact_symb = False
547
552
 
548
553
  def set_options(self, pivoting_threshold: float | None = None) -> None:
@@ -562,13 +567,14 @@ class SolverUMFPACK(Solver):
562
567
  A.indptr = A.indptr.astype(np.int64)
563
568
  A.indices = A.indices.astype(np.int64)
564
569
  if self.fact_symb is False:
565
- logger.debug(f'[ID={id}] Executing symbollic factorization.')
570
+ logger.trace(f'[ID={id}] Executing symbollic factorization.')
566
571
  self.umfpack.symbolic(A)
567
572
  self.fact_symb = True
568
573
  if not reuse_factorization:
569
- #logger.debug('Executing numeric factorization.')
574
+ logger.trace(f'[ID={id}] Executing numeric factorization.')
570
575
  self.umfpack.numeric(A)
571
576
  self.A = A
577
+ logger.trace(f'[ID={id}] Solving linear system.')
572
578
  x = self.umfpack.solve(um.UMFPACK_A, self.A, b, autoTranspose = False ) # ty: ignore
573
579
  aux = {
574
580
  "Pivoting Threshold": str(self._pivoting_threshold),
@@ -596,12 +602,14 @@ class SolverPardiso(Solver):
596
602
  def solve(self, A, b, precon, reuse_factorization: bool = False, id: int = -1) -> tuple[np.ndarray, SolveReport]:
597
603
  logger.info(f'[ID={id}] Calling Pardiso Solver')
598
604
  if self.fact_symb is False:
599
- logger.debug(f'[ID={id}] Executing symbollic factorization.')
605
+ logger.trace(f'[ID={id}] Executing symbollic factorization.')
600
606
  self.solver.symbolic(A)
601
607
  self.fact_symb = True
602
608
  if not reuse_factorization:
609
+ logger.trace(f'[ID={id}] Executing numeric factorization.')
603
610
  self.solver.numeric(A)
604
611
  self.A = A
612
+ logger.trace(f'[ID={id}] Solving linear system.')
605
613
  x, error = self.solver.solve(A, b)
606
614
  if error != 0:
607
615
  logger.error(f'[ID={id}] Terminated with error code {error}')
@@ -634,13 +642,15 @@ class CuDSSSolver(Solver):
634
642
  logger.info(f'[ID={id}] Calling cuDSS Solver')
635
643
 
636
644
  if self.fact_symb is False:
637
- logger.debug('Executing symbollic factorization')
645
+ logger.trace(f'[ID={id}] Starting from symbollic factorization.')
638
646
  x = self._cudss.from_symbolic(A,b)
639
647
  self.fact_symb = True
640
648
  else:
641
649
  if reuse_factorization:
650
+ logger.trace(f'[ID={id}] Solving linear system.')
642
651
  x = self._cudss.from_solve(b)
643
652
  else:
653
+ logger.trace(f'[ID={id}] Starting from numeric factorization.')
644
654
  x = self._cudss.from_numeric(A,b)
645
655
 
646
656
  return x, SolveReport(solver=str(self), exit_code=0, aux={})