1# We don't need to import 'exceptions'
2import os.path
3
4from unyt.exceptions import UnitOperationError
5
6
7class YTException(Exception):
8    def __init__(self, message=None, ds=None):
9        Exception.__init__(self, message)
10        self.ds = ds
11
12
13# Data access exceptions:
14
15
16class YTUnidentifiedDataType(YTException):
17    def __init__(self, filename, *args, **kwargs):
18        self.filename = filename
19        self.args = args
20        self.kwargs = kwargs
21
22    def __str__(self):
23        msg = [f"Could not determine input format from `'{self.filename}'"]
24        if self.args:
25            msg.append(", ".join(str(a) for a in self.args))
26        if self.kwargs:
27            msg.append(", ".join(f"{k}={v}" for k, v in self.kwargs.items()))
28        msg = ", ".join(msg) + "`."
29        return msg
30
31
32class YTOutputNotIdentified(YTUnidentifiedDataType):
33    def __init__(self, filename, args=None, kwargs=None):
34        super(YTUnidentifiedDataType, self).__init__(filename, args, kwargs)
35        # this cannot be imported at the module level (creates circular imports)
36        from yt._maintenance.deprecation import issue_deprecation_warning
37
38        issue_deprecation_warning(
39            "YTOutputNotIdentified is a deprecated alias for YTUnidentifiedDataType",
40            since="4.0.0",
41            removal="4.1.0",
42        )
43
44
45class YTAmbiguousDataType(YTUnidentifiedDataType):
46    def __init__(self, filename, candidates):
47        self.filename = filename
48        self.candidates = candidates
49
50    def __str__(self):
51        msg = f"Multiple data type candidates for {self.filename}\n"
52        msg += "The following independent classes were detected as valid :\n"
53        for c in self.candidates:
54            msg += f"{c}\n"
55        msg += "A possible workaround is to directly instantiate one of the above.\n"
56        msg += "Please report this to https://github.com/yt-project/yt/issues/new"
57        return msg
58
59
60class YTSphereTooSmall(YTException):
61    def __init__(self, ds, radius, smallest_cell):
62        YTException.__init__(self, ds=ds)
63        self.radius = radius
64        self.smallest_cell = smallest_cell
65
66    def __str__(self):
67        return f"{self.radius:0.5e} < {self.smallest_cell:0.5e}"
68
69
70class YTAxesNotOrthogonalError(YTException):
71    def __init__(self, axes):
72        self.axes = axes
73
74    def __str__(self):
75        return f"The supplied axes are not orthogonal.  {self.axes}"
76
77
78class YTNoDataInObjectError(YTException):
79    def __init__(self, obj):
80        self.obj_type = getattr(obj, "_type_name", "")
81
82    def __str__(self):
83        s = "The object requested has no data included in it."
84        if self.obj_type == "slice":
85            s += "  It may lie on a grid face.  Try offsetting slightly."
86        return s
87
88
89class YTFieldNotFound(YTException):
90    def __init__(self, field, ds):
91        self.field = field
92        self.ds = ds
93        self.suggestions = []
94        try:
95            self._find_suggestions()
96        except AttributeError:
97            # This may happen if passing a field that is e.g. an Ellipsis
98            # e.g. when using ds.r[...]
99            pass
100
101    def _find_suggestions(self):
102        from yt.funcs import levenshtein_distance
103
104        field = self.field
105        ds = self.ds
106
107        suggestions = {}
108        if not isinstance(field, tuple):
109            ftype, fname = None, field
110        elif field[1] is None:
111            ftype, fname = None, field[0]
112        else:
113            ftype, fname = field
114
115        # Limit the suggestions to a distance of 3 (at most 3 edits)
116        # This is very arbitrary, but is picked so that...
117        # - small typos lead to meaningful suggestions (e.g. `densty` -> `density`)
118        # - we don't suggest unrelated things (e.g. `pressure` -> `density` has a distance
119        #   of 6, we definitely do not want it)
120        # A threshold of 3 seems like a good middle point.
121        max_distance = 3
122
123        # Suggest (ftype, fname), with alternative ftype
124        for ft, fn in ds.derived_field_list:
125            if fn.lower() == fname.lower() and (
126                ftype is None or ft.lower() != ftype.lower()
127            ):
128                suggestions[ft, fn] = 0
129
130        if ftype is not None:
131            # Suggest close matches using levenshtein distance
132            fields_str = {_: str(_).lower() for _ in ds.derived_field_list}
133            field_str = str(field).lower()
134
135            for (ft, fn), fs in fields_str.items():
136                distance = levenshtein_distance(field_str, fs, max_dist=max_distance)
137                if distance < max_distance:
138                    if (ft, fn) in suggestions:
139                        continue
140                    suggestions[ft, fn] = distance
141
142        # Return suggestions sorted by increasing distance (first are most likely)
143        self.suggestions = [
144            (ft, fn)
145            for (ft, fn), distance in sorted(suggestions.items(), key=lambda v: v[1])
146        ]
147
148    def __str__(self):
149        msg = f"Could not find field {self.field} in {self.ds}."
150        if self.suggestions:
151            msg += "\nDid you mean:\n\t"
152            msg += "\n\t".join(str(_) for _ in self.suggestions)
153        return msg
154
155
156class YTParticleTypeNotFound(YTException):
157    def __init__(self, fname, ds):
158        self.fname = fname
159        self.ds = ds
160
161    def __str__(self):
162        return f"Could not find particle_type '{self.fname}' in {self.ds}."
163
164
165class YTSceneFieldNotFound(YTException):
166    pass
167
168
169class YTCouldNotGenerateField(YTFieldNotFound):
170    def __str__(self):
171        return f"Could field '{self.fname}' in {self.ds} could not be generated."
172
173
174class YTFieldTypeNotFound(YTException):
175    def __init__(self, ftype, ds=None):
176        self.ftype = ftype
177        self.ds = ds
178
179    def __str__(self):
180        if self.ds is not None and self.ftype in self.ds.particle_types:
181            return (
182                "Could not find field type '%s'.  "
183                + "This field type is a known particle type for this dataset.  "
184                + "Try adding this field with particle_type=True."
185            ) % self.ftype
186        else:
187            return f"Could not find field type '{self.ftype}'."
188
189
190class YTSimulationNotIdentified(YTException):
191    def __init__(self, sim_type):
192        YTException.__init__(self)
193        self.sim_type = sim_type
194
195    def __str__(self):
196        return f"Simulation time-series type {self.sim_type} not defined."
197
198
199class YTCannotParseFieldDisplayName(YTException):
200    def __init__(self, field_name, display_name, mathtext_error):
201        self.field_name = field_name
202        self.display_name = display_name
203        self.mathtext_error = mathtext_error
204
205    def __str__(self):
206        return (
207            'The display name "%s" '
208            "of the derived field %s "
209            "contains the following LaTeX parser errors:\n"
210        ) % (self.display_name, self.field_name) + self.mathtext_error
211
212
213class YTCannotParseUnitDisplayName(YTException):
214    def __init__(self, field_name, unit_name, mathtext_error):
215        self.field_name = field_name
216        self.unit_name = unit_name
217        self.mathtext_error = mathtext_error
218
219    def __str__(self):
220        return (
221            'The unit display name "%s" '
222            "of the derived field %s "
223            "contains the following LaTeX parser errors:\n"
224        ) % (self.unit_name, self.field_name) + self.mathtext_error
225
226
227class InvalidSimulationTimeSeries(YTException):
228    def __init__(self, message):
229        self.message = message
230
231    def __str__(self):
232        return self.message
233
234
235class MissingParameter(YTException):
236    def __init__(self, ds, parameter):
237        YTException.__init__(self, ds=ds)
238        self.parameter = parameter
239
240    def __str__(self):
241        return f"dataset {self.ds} is missing {self.parameter} parameter."
242
243
244class NoStoppingCondition(YTException):
245    def __init__(self, ds):
246        YTException.__init__(self, ds=ds)
247
248    def __str__(self):
249        return (
250            "Simulation %s has no stopping condition. "
251            "StopTime or StopCycle should be set." % self.ds
252        )
253
254
255class YTNotInsideNotebook(YTException):
256    def __str__(self):
257        return "This function only works from within an IPython Notebook."
258
259
260class YTGeometryNotSupported(YTException):
261    def __init__(self, geom):
262        self.geom = geom
263
264    def __str__(self):
265        return f"We don't currently support {self.geom} geometry"
266
267
268class YTCoordinateNotImplemented(YTException):
269    def __str__(self):
270        return "This coordinate is not implemented for this geometry type."
271
272
273# define for back compat reasons for code written before yt 4.0
274YTUnitOperationError = UnitOperationError
275
276
277class YTUnitNotRecognized(YTException):
278    def __init__(self, unit):
279        self.unit = unit
280
281    def __str__(self):
282        return f"This dataset doesn't recognize {self.unit}"
283
284
285class YTFieldUnitError(YTException):
286    def __init__(self, field_info, returned_units):
287        self.msg = (
288            "The field function associated with the field '%s' returned "
289            "data with units '%s' but was defined with units '%s'."
290        )
291        self.msg = self.msg % (field_info.name, returned_units, field_info.units)
292
293    def __str__(self):
294        return self.msg
295
296
297class YTFieldUnitParseError(YTException):
298    def __init__(self, field_info):
299        self.msg = "The field '%s' has unparseable units '%s'."
300        self.msg = self.msg % (field_info.name, field_info.units)
301
302    def __str__(self):
303        return self.msg
304
305
306class YTSpatialFieldUnitError(YTException):
307    def __init__(self, field):
308        msg = (
309            "Field '%s' is a spatial field but has unknown units but "
310            "spatial fields must have explicitly defined units. Add the "
311            "field with explicit 'units' to clear this error."
312        )
313        self.msg = msg % (field,)
314
315    def __str__(self):
316        return self.msg
317
318
319class YTHubRegisterError(YTException):
320    def __str__(self):
321        return (
322            "You must create an API key before uploading.  See "
323            + "https://data.yt-project.org/getting_started.html"
324        )
325
326
327class YTNoFilenamesMatchPattern(YTException):
328    def __init__(self, pattern):
329        self.pattern = pattern
330
331    def __str__(self):
332        return f"No filenames were found to match the pattern: '{self.pattern}'"
333
334
335class YTNoOldAnswer(YTException):
336    def __init__(self, path):
337        self.path = path
338
339    def __str__(self):
340        return f"There is no old answer available.\n{self.path}"
341
342
343class YTNoAnswerNameSpecified(YTException):
344    def __init__(self, message=None):
345        if message is None or message == "":
346            message = (
347                "Answer name not provided for the answer testing test."
348                "\n  Please specify --answer-name=<answer_name> in"
349                " command line mode or in AnswerTestingTest.answer_name"
350                " variable."
351            )
352        self.message = message
353
354    def __str__(self):
355        return str(self.message)
356
357
358class YTCloudError(YTException):
359    def __init__(self, path):
360        self.path = path
361
362    def __str__(self):
363        return "Failed to retrieve cloud data. Connection may be broken.\n" + str(
364            self.path
365        )
366
367
368class YTEllipsoidOrdering(YTException):
369    def __init__(self, ds, A, B, C):
370        YTException.__init__(self, ds=ds)
371        self._A = A
372        self._B = B
373        self._C = C
374
375    def __str__(self):
376        return "Must have A>=B>=C"
377
378
379class EnzoTestOutputFileNonExistent(YTException):
380    def __init__(self, filename):
381        self.filename = filename
382        self.testname = os.path.basename(os.path.dirname(filename))
383
384    def __str__(self):
385        return (
386            "Enzo test output file (OutputLog) not generated for: "
387            + f"'{self.testname}'"
388            + ".\nTest did not complete."
389        )
390
391
392class YTNoAPIKey(YTException):
393    def __init__(self, service, config_name):
394        self.service = service
395        self.config_name = config_name
396
397    def __str__(self):
398        return "You need to set an API key for {} in ~/.config/yt/ytrc as {}".format(
399            self.service,
400            self.config_name,
401        )
402
403
404class YTTooManyVertices(YTException):
405    def __init__(self, nv, fn):
406        self.nv = nv
407        self.fn = fn
408
409    def __str__(self):
410        s = f"There are too many vertices ({self.nv}) to upload to Sketchfab. "
411        s += f"Your model has been saved as {self.fn} .  You should upload manually."
412        return s
413
414
415class YTInvalidWidthError(YTException):
416    def __init__(self, width):
417        self.error = f"width ({str(width)}) is invalid"
418
419    def __str__(self):
420        return str(self.error)
421
422
423class YTFieldNotParseable(YTException):
424    def __init__(self, field):
425        self.field = field
426
427    def __str__(self):
428        return f"Cannot identify field {self.field}"
429
430
431class YTDataSelectorNotImplemented(YTException):
432    def __init__(self, class_name):
433        self.class_name = class_name
434
435    def __str__(self):
436        return f"Data selector '{self.class_name}' not implemented."
437
438
439class YTParticleDepositionNotImplemented(YTException):
440    def __init__(self, class_name):
441        self.class_name = class_name
442
443    def __str__(self):
444        return f"Particle deposition method '{self.class_name}' not implemented."
445
446
447class YTDomainOverflow(YTException):
448    def __init__(self, mi, ma, dle, dre):
449        self.mi = mi
450        self.ma = ma
451        self.dle = dle
452        self.dre = dre
453
454    def __str__(self):
455        return "Particle bounds {} and {} exceed domain bounds {} and {}".format(
456            self.mi,
457            self.ma,
458            self.dle,
459            self.dre,
460        )
461
462
463class YTIntDomainOverflow(YTException):
464    def __init__(self, dims, dd):
465        self.dims = dims
466        self.dd = dd
467
468    def __str__(self):
469        return f"Integer domain overflow: {self.dims} in {self.dd}"
470
471
472class YTIllDefinedFilter(YTException):
473    def __init__(self, filter, s1, s2):
474        self.filter = filter
475        self.s1 = s1
476        self.s2 = s2
477
478    def __str__(self):
479        return "Filter '{}' ill-defined.  Applied to shape {} but is shape {}.".format(
480            self.filter,
481            self.s1,
482            self.s2,
483        )
484
485
486class YTIllDefinedParticleFilter(YTException):
487    def __init__(self, filter, missing):
488        self.filter = filter
489        self.missing = missing
490
491    def __str__(self):
492        msg = (
493            '\nThe fields\n\t{},\nrequired by the "{}" particle filter, '
494            "are not defined for this dataset."
495        )
496        f = self.filter
497        return msg.format("\n".join(str(m) for m in self.missing), f.name)
498
499
500class YTIllDefinedBounds(YTException):
501    def __init__(self, lb, ub):
502        self.lb = lb
503        self.ub = ub
504
505    def __str__(self):
506        v = f"The bounds {self.lb:0.3e} and {self.ub:0.3e} are ill-defined. "
507        v += "Typically this happens when a log binning is specified "
508        v += "and zero or negative values are given for the bounds."
509        return v
510
511
512class YTObjectNotImplemented(YTException):
513    def __init__(self, ds, obj_name):
514        self.ds = ds
515        self.obj_name = obj_name
516
517    def __str__(self):
518        v = r"The object type '%s' is not implemented for the dataset "
519        v += r"'%s'."
520        return v % (self.obj_name, self.ds)
521
522
523class YTParticleOutputFormatNotImplemented(YTException):
524    def __str__(self):
525        return "The particle output format is not supported."
526
527
528class YTFileNotParseable(YTException):
529    def __init__(self, fname, line):
530        self.fname = fname
531        self.line = line
532
533    def __str__(self):
534        v = r"Error while parsing file %s at line %s"
535        return v % (self.fname, self.line)
536
537
538class YTRockstarMultiMassNotSupported(YTException):
539    def __init__(self, mi, ma, ptype):
540        self.mi = mi
541        self.ma = ma
542        self.ptype = ptype
543
544    def __str__(self):
545        v = f"Particle type '{self.ptype}' has minimum mass {self.mi:0.3e} and maximum "
546        v += f"mass {self.ma:0.3e}.  Multi-mass particles are not currently supported."
547        return v
548
549
550class YTTooParallel(YTException):
551    def __str__(self):
552        return "You've used too many processors for this dataset."
553
554
555class YTElementTypeNotRecognized(YTException):
556    def __init__(self, dim, num_nodes):
557        self.dim = dim
558        self.num_nodes = num_nodes
559
560    def __str__(self):
561        return "Element type not recognized - dim = {}, num_nodes = {}".format(
562            self.dim,
563            self.num_nodes,
564        )
565
566
567class YTDuplicateFieldInProfile(Exception):
568    def __init__(self, field, new_spec, old_spec):
569        self.field = field
570        self.new_spec = new_spec
571        self.old_spec = old_spec
572
573    def __str__(self):
574        r = f"""Field {self.field} already exists with field spec:
575               {self.old_spec}
576               But being asked to add it with:
577               {self.new_spec}"""
578        return r
579
580
581class YTInvalidPositionArray(Exception):
582    def __init__(self, shape, dimensions):
583        self.shape = shape
584        self.dimensions = dimensions
585
586    def __str__(self):
587        r = f"""Position arrays must be length and shape (N,3).
588               But this one has {self.dimensions} and {self.shape}."""
589        return r
590
591
592class YTIllDefinedCutRegion(Exception):
593    def __init__(self, conditions):
594        self.conditions = conditions
595
596    def __str__(self):
597        r = """Can't mix particle/discrete and fluid/mesh conditions or
598               quantities.  Conditions specified:
599            """
600        r += "\n".join(c for c in self.conditions)
601        return r
602
603
604class YTMixedCutRegion(Exception):
605    def __init__(self, conditions, field):
606        self.conditions = conditions
607        self.field = field
608
609    def __str__(self):
610        r = f"""Can't mix particle/discrete and fluid/mesh conditions or
611               quantities.  Field: {self.field} and Conditions specified:
612            """
613        r += "\n".join(c for c in self.conditions)
614        return r
615
616
617class YTGDFAlreadyExists(Exception):
618    def __init__(self, filename):
619        self.filename = filename
620
621    def __str__(self):
622        return f"A file already exists at {self.filename} and overwrite=False."
623
624
625class YTNonIndexedDataContainer(YTException):
626    def __init__(self, cont):
627        self.cont = cont
628
629    def __str__(self):
630        return (
631            "The data container (%s) is an unindexed type.  "
632            "Operations such as ires, icoords, fcoords and fwidth "
633            "will not work on it." % type(self.cont)
634        )
635
636
637class YTGDFUnknownGeometry(Exception):
638    def __init__(self, geometry):
639        self.geometry = geometry
640
641    def __str__(self):
642        return (
643            """Unknown geometry %i. Please refer to GDF standard
644                  for more information"""
645            % self.geometry
646        )
647
648
649class YTInvalidUnitEquivalence(Exception):
650    def __init__(self, equiv, unit1, unit2):
651        self.equiv = equiv
652        self.unit1 = unit1
653        self.unit2 = unit2
654
655    def __str__(self):
656        return (
657            "The unit equivalence '%s' does not exist for the units '%s' and '%s'."
658            % (self.equiv, self.unit1, self.unit2)
659        )
660
661
662class YTPlotCallbackError(Exception):
663    def __init__(self, callback):
664        self.callback = "annotate_" + callback
665
666    def __str__(self):
667        return f"{self.callback} callback failed"
668
669
670class YTPixelizeError(YTException):
671    def __init__(self, message):
672        self.message = message
673
674    def __str__(self):
675        return self.message
676
677
678class YTDimensionalityError(YTException):
679    def __init__(self, wrong, right):
680        self.wrong = wrong
681        self.right = right
682
683    def __str__(self):
684        return f"Dimensionality specified was {self.wrong} but we need {self.right}"
685
686
687class YTInvalidShaderType(YTException):
688    def __init__(self, source):
689        self.source = source
690
691    def __str__(self):
692        return f"Can't identify shader_type for file '{self.source}.'"
693
694
695class YTInvalidFieldType(YTException):
696    def __init__(self, fields):
697        self.fields = fields
698
699    def __str__(self):
700        msg = (
701            "\nSlicePlot, ProjectionPlot, and OffAxisProjectionPlot can "
702            "only plot fields that\n"
703            "are defined on a mesh or for SPH particles, but received the "
704            "following N-body\n"
705            "particle fields:\n\n"
706            "    %s\n\n"
707            "Did you mean to use ParticlePlot or plot a deposited particle "
708            "field instead?" % self.fields
709        )
710        return msg
711
712
713class YTUnknownUniformKind(YTException):
714    def __init__(self, kind):
715        self.kind = kind
716
717    def __str__(self):
718        return f"Can't determine kind specification for {self.kind}"
719
720
721class YTUnknownUniformSize(YTException):
722    def __init__(self, size_spec):
723        self.size_spec = size_spec
724
725    def __str__(self):
726        return f"Can't determine size specification for {self.size_spec}"
727
728
729class YTDataTypeUnsupported(YTException):
730    def __init__(self, this, supported):
731        self.supported = supported
732        self.this = this
733
734    def __str__(self):
735        v = f"This operation is not supported for data of geometry {self.this}; "
736        v += f"It supports data of geometries {self.supported}"
737        return v
738
739
740class YTBoundsDefinitionError(YTException):
741    def __init__(self, message, bounds):
742        self.bounds = bounds
743        self.message = message
744
745    def __str__(self):
746        v = "This operation has encountered a bounds error: "
747        v += self.message
748        v += f" Specified bounds are '{self.bounds}'."
749        return v
750
751
752def screen_one_element_list(lis):
753    if len(lis) == 1:
754        return lis[0]
755    return lis
756
757
758class YTIllDefinedProfile(YTException):
759    def __init__(self, bin_fields, fields, weight_field, is_pfield):
760        nbin = len(bin_fields)
761        nfields = len(fields)
762        self.bin_fields = screen_one_element_list(bin_fields)
763        self.bin_fields_ptype = screen_one_element_list(is_pfield[:nbin])
764        self.fields = screen_one_element_list(fields)
765        self.fields_ptype = screen_one_element_list(is_pfield[nbin : nbin + nfields])
766        self.weight_field = weight_field
767        if self.weight_field is not None:
768            self.weight_field_ptype = is_pfield[-1]
769
770    def __str__(self):
771        msg = (
772            "\nCannot create a profile object that mixes particle and mesh "
773            "fields.\n\n"
774            "Received the following bin_fields:\n\n"
775            "   %s, particle_type = %s\n\n"
776            "Profile fields:\n\n"
777            "   %s, particle_type = %s\n"
778        )
779        msg = msg % (
780            self.bin_fields,
781            self.bin_fields_ptype,
782            self.fields,
783            self.fields_ptype,
784        )
785
786        if self.weight_field is not None:
787            weight_msg = "\nAnd weight field:\n\n   %s, particle_type = %s\n"
788            weight_msg = weight_msg % (self.weight_field, self.weight_field_ptype)
789        else:
790            weight_msg = ""
791
792        return msg + weight_msg
793
794
795class YTProfileDataShape(YTException):
796    def __init__(self, field1, shape1, field2, shape2):
797        self.field1 = field1
798        self.shape1 = shape1
799        self.field2 = field2
800        self.shape2 = shape2
801
802    def __str__(self):
803        return (
804            "Profile fields must have same shape: %s has "
805            + "shape %s and %s has shape %s."
806        ) % (self.field1, self.shape1, self.field2, self.shape2)
807
808
809class YTBooleanObjectError(YTException):
810    def __init__(self, bad_object):
811        self.bad_object = bad_object
812
813    def __str__(self):
814        v = f"Supplied:\n{self.bad_object}\nto a boolean operation"
815        v += " but it is not a YTSelectionContainer3D object."
816        return v
817
818
819class YTBooleanObjectsWrongDataset(YTException):
820    def __init__(self):
821        pass
822
823    def __str__(self):
824        return "Boolean data objects must share a common dataset object."
825
826
827class YTIllDefinedAMR(YTException):
828    def __init__(self, level, axis):
829        self.level = level
830        self.axis = axis
831
832    def __str__(self):
833        msg = (
834            "Grids on the level {} are not properly aligned with cell edges "
835            "on the parent level ({} axis)"
836        ).format(self.level, self.axis)
837        return msg
838
839
840class YTIllDefinedParticleData(YTException):
841    pass
842
843
844class YTIllDefinedAMRData(YTException):
845    pass
846
847
848class YTInconsistentGridFieldShape(YTException):
849    def __init__(self, shapes):
850        self.shapes = shapes
851
852    def __str__(self):
853        msg = "Not all grid-based fields have the same shape!\n"
854        for name, shape in self.shapes:
855            msg += f"    Field {name} has shape {shape}.\n"
856        return msg
857
858
859class YTInconsistentParticleFieldShape(YTException):
860    def __init__(self, ptype, shapes):
861        self.ptype = ptype
862        self.shapes = shapes
863
864    def __str__(self):
865        msg = ("Not all fields with field type '{}' have the same shape!\n").format(
866            self.ptype
867        )
868        for name, shape in self.shapes:
869            field = (self.ptype, name)
870            msg += f"    Field {field} has shape {shape}.\n"
871        return msg
872
873
874class YTInconsistentGridFieldShapeGridDims(YTException):
875    def __init__(self, shapes, grid_dims):
876        self.shapes = shapes
877        self.grid_dims = grid_dims
878
879    def __str__(self):
880        msg = "Not all grid-based fields match the grid dimensions! "
881        msg += f"Grid dims are {self.grid_dims}, "
882        msg += "and the following fields have shapes that do not match them:\n"
883        for name, shape in self.shapes:
884            if shape != self.grid_dims:
885                msg += f"    Field {name} has shape {shape}.\n"
886        return msg
887
888
889class YTCommandRequiresModule(YTException):
890    def __init__(self, module):
891        self.module = module
892
893    def __str__(self):
894        msg = f'This command requires "{self.module}" to be installed.\n\n'
895        msg += f'Please install "{self.module}" with the package manager '
896        msg += "appropriate for your python environment, e.g.:\n"
897        msg += f"  conda install {self.module}\n"
898        msg += "or:\n"
899        msg += f" python -m pip install {self.module}\n"
900        return msg
901
902
903class YTModuleRemoved(Exception):
904    def __init__(self, name, new_home=None, info=None):
905        message = f"The {name} module has been removed from yt."
906        if new_home is not None:
907            message += f"\nIt has been moved to {new_home}."
908        if info is not None:
909            message += f"\nFor more information, see {info}."
910        Exception.__init__(self, message)
911
912
913class YTArrayTooLargeToDisplay(YTException):
914    def __init__(self, size, max_size):
915        self.size = size
916        self.max_size = max_size
917
918    def __str__(self):
919        msg = f"The requested array is of size {self.size}.\n"
920        msg += "We do not support displaying arrays larger\n"
921        msg += f"than size {self.max_size}."
922        return msg
923
924
925class GenerationInProgress(Exception):
926    def __init__(self, fields):
927        self.fields = fields
928        super().__init__()
929