nonos-cli 0.1.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.
nonos_cli/__init__.py ADDED
@@ -0,0 +1,618 @@
1
+ #!/usr/bin/env python
2
+ """
3
+ Visualization tool for idefix/pluto/fargo3d (M)HD simulations of protoplanetary disks
4
+ """
5
+ # adapted from pbenitez-llambay, gwafflard-fernandez, cmt robert & glesur
6
+
7
+ import argparse
8
+ import functools
9
+ import importlib.resources as importlib_resources
10
+ import os
11
+ import re
12
+ import sys
13
+ import time
14
+ from collections import ChainMap
15
+ from importlib import import_module
16
+ from importlib.metadata import version
17
+ from importlib.util import find_spec
18
+ from multiprocessing import Pool
19
+ from pathlib import Path
20
+ from typing import TYPE_CHECKING, Any
21
+
22
+ import inifix
23
+ import numpy as np
24
+ from tqdm import tqdm
25
+
26
+ from nonos.api import GasDataSet
27
+ from nonos.api._angle_parsing import _parse_planet_file
28
+ from nonos.loaders import loader_from
29
+ from nonos.styling import set_mpl_style
30
+ from nonos_cli.config import DEFAULTS
31
+ from nonos_cli.logging import configure_logger, logger, parse_verbose_level
32
+ from nonos_cli.parsing import (
33
+ is_set,
34
+ parse_image_format,
35
+ parse_output_number_range,
36
+ parse_range,
37
+ range_converter,
38
+ userval_or_default,
39
+ )
40
+
41
+ if TYPE_CHECKING:
42
+ from typing import Literal
43
+
44
+ from matplotlib.backend_bases import FigureCanvasBase
45
+ from matplotlib.figure import Figure
46
+
47
+ LIB_VERSION = version("nonos")
48
+ CLI_VERSION = version("nonos-cli")
49
+
50
+ KNOWN_CMAP_PACKAGE_PREFIXES = {
51
+ "cb": "cblind",
52
+ "cmo": "cmocean",
53
+ "cmr": "cmasher",
54
+ "cmyt": "cmyt",
55
+ }
56
+
57
+
58
+ def get_non_interactive_figure(fmt: str) -> "Figure":
59
+ from matplotlib.backends.backend_agg import FigureCanvasAgg
60
+ from matplotlib.backends.backend_pdf import FigureCanvasPdf
61
+ from matplotlib.backends.backend_ps import FigureCanvasPS
62
+ from matplotlib.backends.backend_svg import FigureCanvasSVG
63
+ from matplotlib.figure import Figure
64
+
65
+ FigureCanvas: type[FigureCanvasBase]
66
+
67
+ if fmt in ["png", "jpg", "jpeg", "raw", "rgba", "tif", "tiff"]:
68
+ FigureCanvas = FigureCanvasAgg
69
+ elif fmt == "pdf":
70
+ FigureCanvas = FigureCanvasPdf
71
+ elif fmt in ["ps", "eps"]:
72
+ FigureCanvas = FigureCanvasPS
73
+ elif fmt == "svg":
74
+ FigureCanvas = FigureCanvasSVG
75
+ else:
76
+ raise ValueError(f"unknown file format {fmt}")
77
+
78
+ fig = Figure()
79
+ FigureCanvas(fig)
80
+ return fig
81
+
82
+
83
+ # process function for parallelisation purpose with progress bar
84
+ # counterParallel = Value('i', 0) # initialization of a counter
85
+ def process_field(
86
+ on,
87
+ operations: list[str],
88
+ field,
89
+ plane,
90
+ geometry,
91
+ diff,
92
+ log,
93
+ planet_file: str | None,
94
+ extent: 'Literal["unset"] | tuple[str, str] | tuple[str, str, str, str]',
95
+ vmin,
96
+ vmax,
97
+ scaling: float,
98
+ cmap: str,
99
+ title,
100
+ unit_conversion: int,
101
+ datadir,
102
+ show: bool,
103
+ dpi: int,
104
+ fmt: str,
105
+ theta,
106
+ z,
107
+ phi,
108
+ distance,
109
+ *,
110
+ log_level,
111
+ ):
112
+ configure_logger(level=log_level)
113
+ set_mpl_style(scaling=scaling)
114
+ if geometry == "unset":
115
+ geometry = None
116
+ ds = GasDataSet(on, geometry=geometry, directory=datadir)
117
+ dsop = ds[field]
118
+ if diff:
119
+ dsop = dsop.diff(0)
120
+ if "vm" in operations:
121
+ dsop = dsop.vertical_at_midplane()
122
+ elif "vp" in operations:
123
+ dsop = dsop.vertical_projection(z=z)
124
+ elif "lt" in operations:
125
+ dsop = dsop.latitudinal_at_theta(theta=theta)
126
+ elif "lp" in operations:
127
+ dsop = dsop.latitudinal_projection(theta=theta)
128
+ elif "vz" in operations:
129
+ dsop = dsop.vertical_at_z(z=z)
130
+
131
+ if "ap" in operations:
132
+ dsop = dsop.azimuthal_at_phi(phi=phi)
133
+ elif "apl" in operations:
134
+ dsop = dsop.azimuthal_at_planet(planet_file=planet_file)
135
+ elif "aa" in operations:
136
+ dsop = dsop.azimuthal_average()
137
+
138
+ if "rr" in operations:
139
+ dsop = dsop.radial_at_r(distance=distance)
140
+
141
+ logger.debug("operations performed: {}", operations)
142
+
143
+ dim = 3 - dsop.shape.count(1)
144
+ logger.debug("plotting a {}D plot.", dim)
145
+
146
+ if plane is None:
147
+ dsop_dict = dsop.coords.to_dict()
148
+ default_plane = []
149
+ for key, val in dsop_dict.items():
150
+ if not isinstance(val, str) and val.shape[0] > 2:
151
+ default_plane.append(key)
152
+ # default_plane = ["x","y"]
153
+ plane = default_plane
154
+
155
+ if show:
156
+ import matplotlib.pyplot as plt
157
+
158
+ fig = plt.figure()
159
+ else:
160
+ fig = get_non_interactive_figure(fmt)
161
+
162
+ plot_kwargs = {
163
+ "log": log,
164
+ "vmin": vmin,
165
+ "vmax": vmax,
166
+ "cmap": cmap,
167
+ "title": f"${title}$",
168
+ "unit_conversion": unit_conversion,
169
+ }
170
+
171
+ if cm_prefix := cmap.rpartition(".")[0]:
172
+ if (cm_package := KNOWN_CMAP_PACKAGE_PREFIXES.get(cm_prefix)) is None:
173
+ logger.warning(
174
+ "requested colormap {cmap!r} with the unknown prefix {cm_prefix!r}. "
175
+ "The default colormap will be used instead.",
176
+ cmap=cmap,
177
+ cm_prefix=cm_prefix,
178
+ )
179
+ plot_kwargs.pop("cmap")
180
+ elif not find_spec(cm_package):
181
+ logger.warning(
182
+ "requested colormap {cmap!r}, but {cm_package} is not installed. "
183
+ "The default colormap will be used instead.",
184
+ cmap=cmap,
185
+ cm_package=cm_package,
186
+ )
187
+ plot_kwargs.pop("cmap")
188
+ else:
189
+ # all known colormap packages work by registering their colormaps
190
+ # at import time.
191
+ import_module(cm_package)
192
+
193
+ ax = fig.add_subplot(111, polar=False)
194
+ if dim == 1:
195
+ if "cmap" in plot_kwargs:
196
+ plot_kwargs.pop("cmap")
197
+
198
+ plotable = dsop.map(plane[0], rotate_with=planet_file)
199
+ plotable.plot(fig, ax, **plot_kwargs)
200
+ avalue = plotable.abscissa.data
201
+ # mypy doesn't narrow int to Literal[1] (it should)
202
+ extent_parsed = parse_range(extent, dim=dim) # type: ignore [call-overload]
203
+ extent_parsed = range_converter(
204
+ extent_parsed,
205
+ abscissa=avalue,
206
+ ordinate=np.zeros(2),
207
+ )
208
+ ax.set_xlim(extent_parsed[0], extent_parsed[1])
209
+ elif dim == 2:
210
+ dsop.map(plane[0], plane[1], rotate_with=planet_file).plot(
211
+ fig, ax, **plot_kwargs
212
+ )
213
+ plotable = dsop.map(plane[0], plane[1], rotate_with=planet_file)
214
+ avalue = plotable.abscissa.data
215
+ ovalue = plotable.ordinate.data
216
+ # mypy doesn't narrow int to Literal[2] (it should)
217
+ extent_parsed = parse_range(extent, dim=dim) # type: ignore [call-overload]
218
+ extent_parsed = range_converter(
219
+ extent_parsed,
220
+ abscissa=avalue,
221
+ ordinate=ovalue,
222
+ )
223
+ ax.set_xlim(extent_parsed[0], extent_parsed[1])
224
+ ax.set_ylim(extent_parsed[2], extent_parsed[3])
225
+ else:
226
+ raise ValueError("Got {dim=}, expected 1 or 2")
227
+
228
+ logger.debug("processed the data before plotting.")
229
+
230
+ if "x" and "y" in plane:
231
+ ax.set_aspect("equal")
232
+
233
+ if show:
234
+ import matplotlib.pyplot as plt
235
+
236
+ plt.show()
237
+ plt.close(fig)
238
+ else:
239
+ logger.debug("saving plot: started")
240
+ filename = f"{''.join(plane)}_{field}_{'_'.join(operations)}{'_diff' if diff else '_'}{'_log' if log else ''}{on:04d}.{fmt}"
241
+ fig.savefig(filename, bbox_inches="tight", dpi=dpi)
242
+ logger.debug("saving plot: finished ({})", filename)
243
+
244
+
245
+ def get_parser() -> argparse.ArgumentParser:
246
+ parser = argparse.ArgumentParser(
247
+ prog="nonos",
248
+ description=__doc__,
249
+ )
250
+
251
+ # TODO: pass this as kwarg when support for Python 3.13 is dropped
252
+ # https://docs.python.org/3.14/library/argparse.html#suggest-on-error
253
+ parser.suggest_on_error = True # type: ignore [attr-defined]
254
+
255
+ parser.add_argument(
256
+ "-dir",
257
+ dest="datadir",
258
+ help=f"location of output files and param files (default: '{DEFAULTS['datadir']}').",
259
+ )
260
+ parser.add_argument(
261
+ "-field",
262
+ # choices=["RHO", "VX1", "VX2", "VX3", "BX1", "BX2", "BX3", "PRS"],
263
+ help=f"name of field to plot (default: '{DEFAULTS['field']}').",
264
+ )
265
+ parser.add_argument(
266
+ "-geometry",
267
+ type=str,
268
+ choices=["polar", "cylindrical", "spherical", "cartesian"],
269
+ help=f"if the geometry of idefix outputs is not recognized (default: '{DEFAULTS['geometry']}').",
270
+ )
271
+ parser.add_argument(
272
+ "-operation",
273
+ type=str,
274
+ nargs="+",
275
+ choices=["vm", "vp", "vz", "lt", "lp", "aa", "ap", "apl", "rr"],
276
+ help=f"operation to apply to the fild (default: '{DEFAULTS['operation']}').",
277
+ )
278
+ parser.add_argument(
279
+ "-plane",
280
+ type=str,
281
+ nargs="+",
282
+ help=f"abscissa and ordinate of the plane of projection (default: '{DEFAULTS['plane']}'), example: r phi",
283
+ )
284
+
285
+ # TODO(GWF): add support for -rotate_by -1.2453036989845032 (idefix_newvtk_planet2d)
286
+ parser.add_argument(
287
+ "-corotate",
288
+ type=int,
289
+ default=None,
290
+ help="planet number that defines with which planet the grid corotates.",
291
+ )
292
+ parser.add_argument(
293
+ "-range",
294
+ type=str,
295
+ nargs="+",
296
+ help=f"range of matplotlib window (default: {DEFAULTS['range']}), example: x x -2 2",
297
+ )
298
+ parser.add_argument(
299
+ "-vmin",
300
+ type=float,
301
+ help=f"min value (default: {DEFAULTS['vmin']})",
302
+ )
303
+ parser.add_argument(
304
+ "-vmax",
305
+ type=float,
306
+ help=f"max value (default: {DEFAULTS['vmax']})",
307
+ )
308
+ parser.add_argument(
309
+ "-theta",
310
+ type=float,
311
+ help=f"if latitudinal operation (default: {DEFAULTS['theta']})",
312
+ )
313
+ parser.add_argument(
314
+ "-z",
315
+ type=float,
316
+ help=f"if vertical operation (default: {DEFAULTS['z']})",
317
+ )
318
+ parser.add_argument(
319
+ "-phi",
320
+ type=float,
321
+ help=f"if azimuthal operation (default: {DEFAULTS['phi']})",
322
+ )
323
+ parser.add_argument(
324
+ "-distance",
325
+ type=float,
326
+ help=f"if radial operation (default: {DEFAULTS['distance']})",
327
+ )
328
+ parser.add_argument(
329
+ "-cpu",
330
+ "-ncpu",
331
+ dest="ncpu",
332
+ type=int,
333
+ help=f"number of parallel processes (default: {DEFAULTS['ncpu']}).",
334
+ )
335
+
336
+ select_group = parser.add_mutually_exclusive_group()
337
+ select_group.add_argument(
338
+ "-on",
339
+ type=int,
340
+ nargs="+",
341
+ help="output number(s) (on) to plot. "
342
+ "This can be a single value or a range (start, end, [step]) where both ends are inclusive. "
343
+ "(default: last output available).",
344
+ )
345
+ select_group.add_argument(
346
+ "-all",
347
+ action="store_true",
348
+ help="save an image for every available snapshot (this will force show=False).",
349
+ )
350
+
351
+ # boolean flags use False as a default value (...by default)
352
+ # forcing them to None instead will allow them to be pruned
353
+ # when we build the ChainMap config
354
+ flag_group = parser.add_argument_group("boolean flags")
355
+ flag_group.add_argument(
356
+ "-diff",
357
+ action="store_true",
358
+ default=None,
359
+ help="plot the relative perturbation of the field f, i.e. (f-f0)/f0.",
360
+ )
361
+ flag_group.add_argument(
362
+ "-log",
363
+ action="store_true",
364
+ default=None,
365
+ help="plot the log10 of the field f, i.e. log(f).",
366
+ )
367
+ flag_group.add_argument(
368
+ "-pbar",
369
+ dest="progressBar",
370
+ action="store_true",
371
+ default=None,
372
+ help="display a progress bar",
373
+ )
374
+
375
+ parser.add_argument(
376
+ "-scaling",
377
+ dest="scaling",
378
+ type=float,
379
+ help=f"scale the overall sizes of features in the graph (fonts, linewidth...) (default: {DEFAULTS['scaling']}).",
380
+ )
381
+ parser.add_argument(
382
+ "-cmap",
383
+ help=f"choice of colormap for the 2D maps (default: '{DEFAULTS['cmap']}').",
384
+ )
385
+ parser.add_argument(
386
+ "-title",
387
+ type=str,
388
+ help=f"name of the field in the colorbar for the 2D maps (default: '{DEFAULTS['title']}').",
389
+ )
390
+ parser.add_argument(
391
+ "-uc",
392
+ "-unit_conversion",
393
+ dest="unit_conversion",
394
+ type=float,
395
+ help=f"conversion factor for the considered quantity (default: '{DEFAULTS['unit_conversion']}').",
396
+ )
397
+ parser.add_argument(
398
+ "-fmt",
399
+ "-format",
400
+ dest="format",
401
+ help=f"select output image file format (default: {DEFAULTS['format']})",
402
+ )
403
+ parser.add_argument(
404
+ "-dpi",
405
+ type=int,
406
+ help=f"image file resolution (default: {DEFAULTS['dpi']})",
407
+ )
408
+
409
+ cli_only_group = parser.add_argument_group("CLI-only options")
410
+ cli_input_group = cli_only_group.add_mutually_exclusive_group()
411
+ cli_input_group.add_argument(
412
+ "-input", "-i", dest="input", type=str, help="specify a configuration file."
413
+ )
414
+ cli_input_group.add_argument(
415
+ "-isolated", action="store_true", help="ignore any existing 'nonos.ini' file."
416
+ )
417
+ cli_action_group = cli_only_group.add_mutually_exclusive_group()
418
+ cli_action_group.add_argument(
419
+ "-d",
420
+ "-display",
421
+ dest="display",
422
+ action="store_true",
423
+ help="open a graphic window with the plot (only works with a single image)",
424
+ )
425
+ cli_action_group.add_argument(
426
+ "-version",
427
+ "--version",
428
+ action="store_true",
429
+ help="show raw version number and exit",
430
+ )
431
+ cli_action_group.add_argument(
432
+ "-logo",
433
+ action="store_true",
434
+ help="show Nonos logo with version number, and exit.",
435
+ )
436
+ cli_action_group.add_argument(
437
+ "-config", action="store_true", help="show configuration and exit."
438
+ )
439
+
440
+ cli_debug_group = cli_only_group.add_mutually_exclusive_group()
441
+ cli_debug_group.add_argument(
442
+ "-v",
443
+ "-verbose",
444
+ "--verbose",
445
+ action="count",
446
+ default=0,
447
+ help="increase output verbosity (-v: info, -vv: debug).",
448
+ )
449
+ return parser
450
+
451
+
452
+ def main(argv: list[str] | None = None) -> int:
453
+ parser = get_parser()
454
+ clargs = vars(parser.parse_args(argv))
455
+
456
+ # special cases: destructively consume CLI-only arguments with dict.pop
457
+
458
+ if clargs.pop("logo"):
459
+ logo = importlib_resources.files("nonos_cli").joinpath("logo.txt").read_text()
460
+ print(f"{logo}{__doc__}")
461
+ print(f"nonos-cli {CLI_VERSION}")
462
+ print(f"nonos {LIB_VERSION}")
463
+ return 0
464
+
465
+ if clargs.pop("version"):
466
+ print(f"nonos-cli {CLI_VERSION}")
467
+ print(f"nonos {LIB_VERSION}")
468
+ return 0
469
+
470
+ # clargs.pop("verbose")
471
+ level = parse_verbose_level(clargs.pop("verbose"))
472
+ configure_logger(level=level)
473
+
474
+ if clargs.pop("isolated"):
475
+ config_file_args: dict[str, Any] = {}
476
+ elif (ifile := clargs.pop("input")) is not None:
477
+ if not Path(ifile).is_file():
478
+ logger.error("Couldn't find requested input file {!r}.", ifile)
479
+ return 1
480
+ logger.warning("Using parameters from {!r}.", ifile)
481
+ config_file_args = inifix.load(ifile)
482
+ elif Path("nonos.ini").is_file():
483
+ logger.warning("Using parameters from 'nonos.ini'.")
484
+ config_file_args = inifix.load("nonos.ini")
485
+ else:
486
+ config_file_args = {}
487
+
488
+ # check that every parameter in the configuration is also exposed to the CLI
489
+ assert not set(DEFAULTS).difference(set(clargs))
490
+
491
+ # squeeze out any unset value form cli config to leave room for file parameters
492
+ clargs = {k: v for k, v in clargs.items() if v is not None}
493
+
494
+ # NOTE: init.config is also a ChainMap instance with a default layer
495
+ # this may be seen either as hyperstatism (good thing) or error prone redundancy (bad thing)
496
+ args = ChainMap(clargs, config_file_args, DEFAULTS)
497
+ if clargs.pop("config"):
498
+ conf_repr = {}
499
+ for key in DEFAULTS:
500
+ conf_repr[key] = args[key]
501
+ print(f"# Generated with nonos {LIB_VERSION} + nonos-cli {CLI_VERSION}")
502
+ s = inifix.dumps(conf_repr)
503
+ print(inifix.format_string(s))
504
+ return 0
505
+
506
+ try:
507
+ loader = loader_from(directory=args["datadir"])
508
+ except (FileNotFoundError, RuntimeError, ValueError) as exc:
509
+ logger.error("{}", exc)
510
+ return 1
511
+
512
+ data_files = loader.binary_reader.get_bin_files(args["datadir"])
513
+
514
+ available = set()
515
+ for fn in data_files:
516
+ if (num := re.search(r"\d+", fn.name)) is not None:
517
+ available.add(int(num.group()))
518
+
519
+ if args.pop("all"):
520
+ requested = available
521
+ else:
522
+ try:
523
+ requested = set(
524
+ parse_output_number_range(args["on"], maxval=max(available))
525
+ )
526
+ except ValueError as exc:
527
+ logger.error("{}", exc)
528
+ return 1
529
+
530
+ if not (toplot := list(requested.intersection(available))):
531
+ logger.error(
532
+ "No requested output file was found (requested {}, found {}).",
533
+ requested,
534
+ available,
535
+ )
536
+ return 1
537
+ args["on"] = toplot
538
+
539
+ if (show := clargs.pop("display")) and len(args["on"]) > 1:
540
+ logger.warning(
541
+ "display mode can not be used with multiple images, turning it off."
542
+ )
543
+ show = False
544
+
545
+ if not show:
546
+ try:
547
+ args["format"] = parse_image_format(args["format"])
548
+ except ValueError as exc:
549
+ logger.error("{}", exc)
550
+ return 1
551
+
552
+ # check that every CLI-only argument was consumed at this point
553
+ assert not set(clargs).difference(set(DEFAULTS))
554
+
555
+ args["field"] = args["field"].upper()
556
+ extent = args["range"]
557
+
558
+ if args["ncpu"] > (ncpu := min(args["ncpu"], os.cpu_count())):
559
+ logger.warning(
560
+ "Requested {args_ncpu}, but the runner only has access to {ncpu}.",
561
+ args_ncpu=args["ncpu"],
562
+ ncpu=ncpu,
563
+ )
564
+
565
+ planet_file: str | None
566
+ if not is_set(args["corotate"]):
567
+ planet_file = None
568
+ else:
569
+ planet_file = _parse_planet_file(planet_number=args["corotate"])
570
+
571
+ # call of the process_field function, whether it be in parallel or not
572
+ # TODO: reduce this to the bare minimum
573
+ func_kwargs = dict( # noqa: C408
574
+ operations=userval_or_default(args["operation"], default=["vm"]),
575
+ field=args["field"],
576
+ plane=userval_or_default(args["plane"], default=None),
577
+ geometry=userval_or_default(args["geometry"], default="unset"),
578
+ diff=args["diff"],
579
+ log=args["log"],
580
+ planet_file=planet_file,
581
+ extent=extent,
582
+ vmin=userval_or_default(args["vmin"], default=None),
583
+ vmax=userval_or_default(args["vmax"], default=None),
584
+ scaling=args["scaling"],
585
+ cmap=args["cmap"],
586
+ title=userval_or_default(args["title"], default=args["field"]),
587
+ unit_conversion=args["unit_conversion"],
588
+ datadir=args["datadir"],
589
+ show=show,
590
+ dpi=args["dpi"],
591
+ fmt=args["format"],
592
+ theta=userval_or_default(args["theta"], default=None),
593
+ z=userval_or_default(args["z"], default=None),
594
+ phi=userval_or_default(args["phi"], default=None),
595
+ distance=userval_or_default(args["distance"], default=None),
596
+ log_level=level,
597
+ )
598
+
599
+ progress = functools.partial(
600
+ tqdm if args["progressBar"] else lambda it, *_arg, **_kwargs: it,
601
+ desc="Processing snapshots",
602
+ total=len(args["on"]),
603
+ file=sys.stdout,
604
+ )
605
+
606
+ logger.info("Starting main loop")
607
+ tstart = time.time()
608
+ if ncpu == 1:
609
+ for on in progress(args["on"]):
610
+ process_field(on, **func_kwargs) # pyright: ignore[reportArgumentType]
611
+ else:
612
+ func = functools.partial(process_field, **func_kwargs)
613
+ with Pool(ncpu) as pool:
614
+ list(progress(pool.imap(func, args["on"])))
615
+ if not show:
616
+ logger.info("Operation took {:.2f}s", time.time() - tstart)
617
+
618
+ return 0
nonos_cli/__main__.py ADDED
@@ -0,0 +1,6 @@
1
+ import sys
2
+
3
+ from nonos_cli import main
4
+
5
+ if __name__ == "__main__":
6
+ sys.exit(main())
nonos_cli/config.py ADDED
@@ -0,0 +1,50 @@
1
+ DEFAULTS = {
2
+ # directory where the simulation results are stored
3
+ "datadir": ".",
4
+ # which field?
5
+ "field": "RHO",
6
+ # which operation?
7
+ "operation": "unset",
8
+ # if latitudinal operation
9
+ "theta": "unset",
10
+ # if vertical operation
11
+ "z": "unset",
12
+ # if azimuthal operation
13
+ "phi": "unset",
14
+ # if radial operation
15
+ "distance": "unset",
16
+ # if the geometry of idefix outputs is not recognized
17
+ "geometry": "unset",
18
+ # a single output number ('on') to display or a range (min, max, [step])
19
+ "on": "unset",
20
+ # perturbation of field
21
+ "diff": False,
22
+ # field in log
23
+ "log": False,
24
+ # default extent of the matplotlib window
25
+ "range": "unset",
26
+ # default min field
27
+ "vmin": "unset",
28
+ # default max field
29
+ "vmax": "unset",
30
+ # plane to represent (default is midplane)
31
+ "plane": "unset",
32
+ # do we display the progress (loading+plotting)
33
+ "progressBar": False,
34
+ # do the grid rotate with the planet?
35
+ "corotate": "unset",
36
+ # number of cpus to use
37
+ "ncpu": 1,
38
+ # scaling factor for font size of text in graphs (among other things)
39
+ "scaling": 1,
40
+ # choice of colormap
41
+ "cmap": "RdYlBu_r",
42
+ # name of colorbar
43
+ "title": "unset",
44
+ # conversion factor
45
+ "unit_conversion": 1,
46
+ # select image file format
47
+ "format": "unset",
48
+ # select image resolution
49
+ "dpi": 200,
50
+ }