py-pluto 1.1.4__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 (73) hide show
  1. pyPLUTO/__init__.py +22 -0
  2. pyPLUTO/amr.py +745 -0
  3. pyPLUTO/baseloadmixin.py +258 -0
  4. pyPLUTO/baseloadstate.py +45 -0
  5. pyPLUTO/codes/echo_load.py +161 -0
  6. pyPLUTO/configure.py +261 -0
  7. pyPLUTO/gui/config.py +174 -0
  8. pyPLUTO/gui/custom_var.py +435 -0
  9. pyPLUTO/gui/globals.py +108 -0
  10. pyPLUTO/gui/main.py +17 -0
  11. pyPLUTO/gui/main_window.py +177 -0
  12. pyPLUTO/gui/panels.py +66 -0
  13. pyPLUTO/gui/utils.py +273 -0
  14. pyPLUTO/h_pypluto.py +84 -0
  15. pyPLUTO/image.py +302 -0
  16. pyPLUTO/imagefuncs/colorbar.py +240 -0
  17. pyPLUTO/imagefuncs/contour.py +254 -0
  18. pyPLUTO/imagefuncs/create_axes.py +464 -0
  19. pyPLUTO/imagefuncs/display.py +306 -0
  20. pyPLUTO/imagefuncs/figure.py +395 -0
  21. pyPLUTO/imagefuncs/imagetools.py +487 -0
  22. pyPLUTO/imagefuncs/interactive.py +403 -0
  23. pyPLUTO/imagefuncs/legend.py +250 -0
  24. pyPLUTO/imagefuncs/plot.py +311 -0
  25. pyPLUTO/imagefuncs/range.py +242 -0
  26. pyPLUTO/imagefuncs/scatter.py +270 -0
  27. pyPLUTO/imagefuncs/set_axis.py +497 -0
  28. pyPLUTO/imagefuncs/streamplot.py +297 -0
  29. pyPLUTO/imagefuncs/zoom.py +428 -0
  30. pyPLUTO/imagemixin.py +259 -0
  31. pyPLUTO/imagestate.py +45 -0
  32. pyPLUTO/load.py +447 -0
  33. pyPLUTO/loadfuncs/baseloadtools.py +71 -0
  34. pyPLUTO/loadfuncs/codeselection.py +48 -0
  35. pyPLUTO/loadfuncs/defpluto.py +123 -0
  36. pyPLUTO/loadfuncs/descriptor.py +102 -0
  37. pyPLUTO/loadfuncs/findfiles.py +182 -0
  38. pyPLUTO/loadfuncs/findformat.py +245 -0
  39. pyPLUTO/loadfuncs/initload.py +203 -0
  40. pyPLUTO/loadfuncs/loadvars.py +227 -0
  41. pyPLUTO/loadfuncs/offsetdata.py +87 -0
  42. pyPLUTO/loadfuncs/offsetfluid.py +408 -0
  43. pyPLUTO/loadfuncs/read_files.py +213 -0
  44. pyPLUTO/loadfuncs/readdata.py +619 -0
  45. pyPLUTO/loadfuncs/readdata_old.py +567 -0
  46. pyPLUTO/loadfuncs/readdefplini.py +101 -0
  47. pyPLUTO/loadfuncs/readfluid.py +479 -0
  48. pyPLUTO/loadfuncs/readformat.py +277 -0
  49. pyPLUTO/loadfuncs/readgridalone.py +224 -0
  50. pyPLUTO/loadfuncs/readgridfile.py +255 -0
  51. pyPLUTO/loadfuncs/readgridout.py +451 -0
  52. pyPLUTO/loadfuncs/readpart.py +419 -0
  53. pyPLUTO/loadfuncs/readtab.py +105 -0
  54. pyPLUTO/loadfuncs/write_files.py +283 -0
  55. pyPLUTO/loadmixin.py +419 -0
  56. pyPLUTO/loadpart.py +233 -0
  57. pyPLUTO/loadstate.py +68 -0
  58. pyPLUTO/newload.py +81 -0
  59. pyPLUTO/pytools.py +145 -0
  60. pyPLUTO/toolfuncs/findlines.py +551 -0
  61. pyPLUTO/toolfuncs/fourier.py +149 -0
  62. pyPLUTO/toolfuncs/nabla.py +676 -0
  63. pyPLUTO/toolfuncs/parttools.py +152 -0
  64. pyPLUTO/toolfuncs/transform.py +638 -0
  65. pyPLUTO/utils/annotator.py +27 -0
  66. pyPLUTO/utils/inspector.py +145 -0
  67. pyPLUTO/utils/make_docstrings.py +3 -0
  68. py_pluto-1.1.4.dist-info/METADATA +218 -0
  69. py_pluto-1.1.4.dist-info/RECORD +73 -0
  70. py_pluto-1.1.4.dist-info/WHEEL +5 -0
  71. py_pluto-1.1.4.dist-info/entry_points.txt +2 -0
  72. py_pluto-1.1.4.dist-info/licenses/LICENSE +27 -0
  73. py_pluto-1.1.4.dist-info/top_level.txt +1 -0
pyPLUTO/loadpart.py ADDED
@@ -0,0 +1,233 @@
1
+ from pathlib import Path
2
+ from typing import Any
3
+
4
+ from numpy.typing import NDArray
5
+
6
+
7
+ class LoadPart:
8
+ """Load the particles from the simulation. The class is used to load
9
+ the particles from the simulation and store the data in the class
10
+ attributes. The data are loaded in a memory mapped numpy
11
+ multidimensional array. Such approach does not load the full data
12
+ until needed. Basic operations (i.e. no numpy) are possible, as well
13
+ as slicing the arrays, without fully loading the data. At the
14
+ moment, only one output can be loaded at a time.
15
+
16
+ Returns
17
+ -------
18
+ - None
19
+
20
+ Parameters
21
+ ----------
22
+ - datatype: str, default None
23
+ The format of the data files to be loaded. If None, the code
24
+ finds the format between dbl, flt and vtk.
25
+ - endian: str | None, default None
26
+ The endianess of the data files. If None, the code finds the
27
+ endianess.
28
+ - nfile_lp: int | None, default None
29
+ The file number for the lp methods. If None, the code finds the
30
+ file number.
31
+ - nout: int | str | list | None, default 'last'
32
+ The output number to be loaded. If 'last' the last output is loaded.
33
+ If None, the data are not loaded.
34
+ - path: str, default './'
35
+ The path to the simulation directory.
36
+ - text: bool, default True
37
+ If True, the folder and output are printed.
38
+ In case the user needs a more detailed information of the structure
39
+ and attributes loaded from the class, the __str__ method provides a
40
+ easy display of all the important information.
41
+ - vars: str | list | bool | None, default True
42
+ The variables to be loaded. If True, all the variables are loaded.
43
+ If None, the data are not loaded.
44
+
45
+ ----
46
+
47
+ Examples
48
+ --------
49
+ - Example #1: Load the last output from the simulation
50
+
51
+ >>> LoadPart()
52
+
53
+ - Example #2: Load the last output from the simulation with a specific
54
+ endianess
55
+
56
+ >>> LoadPart(endian="big")
57
+
58
+ - Example #3: Load the last output from the simulation with a specific
59
+ set of variables
60
+
61
+ >>> LoadPart(vars=["rho", "vx", "vy", "vz"])
62
+
63
+ - Example #4: Load the last output from the simulation without printing
64
+ the folder and the specific output loaded
65
+
66
+ >>> LoadPart(0, text=False)
67
+
68
+ - Example #5: Load the last output from the simulation without loading
69
+ the data
70
+
71
+ >>> LoadPart(nout=None)
72
+
73
+ - Example #6: Load the last output from the simulation with a specific
74
+ file number for the lp methods
75
+
76
+ >>> LoadPart(nfile_lp=1)
77
+
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ nout: int | str | None = "last",
83
+ path: str | Path = "./",
84
+ datatype: str | None = None,
85
+ vars: str | list[str] | bool | None = True,
86
+ text: bool = True,
87
+ endian: str | None = None,
88
+ nfile_lp: int | None = None,
89
+ ) -> None:
90
+ # Check if the user wants to load the data
91
+ if nout is None:
92
+ return
93
+
94
+ # Initialization or declaration of variables (used in this file)
95
+ self.nout: NDArray # Output to be loaded
96
+ self._d_end: dict[str | None, str | None] # Endianess dictionary
97
+ self._multiple: bool # Bool for single or multiple files
98
+ self._alone: bool = True # Bool for standalone files
99
+ self._info: bool # Bool for info (linked to alone)
100
+ self._d_vars: dict = {} # The dictionary of variables
101
+
102
+ # Initialization or declaration of variables (used in other files)
103
+ self.pathdir: Path # Path to the simulation directory
104
+ self.format: str | None = None # The format of the files to be loaded
105
+ self._charsize: int # The data size in the files
106
+ self.outlist: NDArray # The list of outputs to be loaded
107
+ self.timelist: NDArray # The list of times to be loaded
108
+ self._lennout: int # The number of outputs to be loaded
109
+ self.ntime: NDArray # The time array
110
+ self._d_info: dict[str, Any] # Info dictionary
111
+ self.set_vars: set[str] # The set of variables to be loaded
112
+ self.set_outs: set[int] # The set of outputs to be loaded
113
+ self._matching_files: list[str] # The list of files to be loaded
114
+ self._pathgrid: Path # Path to the grid file
115
+ self._pathdata: Path | None = (
116
+ None # Path to the data files to be loaded
117
+ )
118
+ self._filepath: Path # The filepath to be loaded
119
+ self._load_vars: list[str] # The list of variables to be loaded
120
+ self._offset: dict[str, int] # The offset of the variables
121
+ self._shape: dict[str, tuple[int, ...]] # The shape of the variables
122
+ self.geom: str # The geometry of the simulation
123
+ self.dim: int # The dimension of the simulation
124
+ self.nshp: int | tuple[int, ...] # The shape of the grid
125
+ self._dictdim: dict # The dictionary of dimensions
126
+ self.nfile_lp: int | None = nfile_lp # File number for the lp methods
127
+ self.maxpart: int = 0 # Max number of particles in the simulation
128
+ self._vardim: NDArray # The dimension of the variables
129
+
130
+ # Check the input endianess
131
+ self._d_end = {
132
+ "big": ">",
133
+ "little": "<",
134
+ ">": ">",
135
+ "<": "<",
136
+ None: None,
137
+ } # Endianess dictionary
138
+
139
+ if endian not in self._d_end.keys():
140
+ raise ValueError(
141
+ f"Invalid endianess. \
142
+ Valid values are {self._d_end.keys()}"
143
+ )
144
+
145
+ if not isinstance(nfile_lp, int) and nfile_lp is not None:
146
+ raise ValueError(
147
+ "Invalid value for nfile_lp. \
148
+ Valid values are int or None"
149
+ )
150
+
151
+ # Check the path and verify that it is a folder
152
+ self._check_pathformat(path)
153
+
154
+ # Find the format of the data files
155
+ self._find_format(datatype, True)
156
+
157
+ # Find relevant informations without opening the files (e.g.
158
+ # the number of files to be loaded)
159
+ self._findfiles(nout)
160
+
161
+ # For every output, load the desired variables and store
162
+ # them in the class
163
+ for i, exout in enumerate(self.nout):
164
+ self._load_variables(vars, i, exout, endian)
165
+ if self.format != "vtk":
166
+ self._store_bin_particles(i)
167
+ else:
168
+ self._store_vtk_particles(i)
169
+
170
+ # Assign the variables to the class
171
+ for key in self._d_vars:
172
+ setattr(self, key, self._d_vars[key])
173
+
174
+ # Change the id variable to int depending on the format loaded
175
+ if self.format != "vtk":
176
+ self.id = self.id.astype("int")
177
+ else:
178
+ self.id = self.id.view(">i4")
179
+
180
+ # Print loaded folder and output
181
+ if text is not False:
182
+ _nout_out = self.nout[0] if len(self.nout) == 1 else list(self.nout)
183
+ print(f"LoadPart: folder {path}, output {_nout_out}")
184
+ return
185
+
186
+ def __str__(self):
187
+ text = f"""
188
+ LoadPart class.
189
+ It loads the particles.
190
+
191
+ File properties:
192
+ - Current path loaded (pathdir) {self.pathdir}
193
+ - Format loaded (format) {self.format}
194
+
195
+ Simulation properties
196
+ - N. particles (maxpart) {self.maxpart}
197
+ - Output loaded (nout) {self.nout}
198
+ - Time loaded (ntime) {self.ntime}
199
+
200
+ Variables loaded:
201
+ {self._d_vars.keys()}
202
+
203
+ Public methods available:
204
+
205
+ - select
206
+ - spectrum
207
+
208
+ Please refrain from using "private" methods and attributes.
209
+ """
210
+ return text
211
+
212
+ def __getattr__(self, name):
213
+ try:
214
+ return object.__getattribute__(self, f"_{name}")
215
+ except AttributeError:
216
+ raise AttributeError(f"'LoadPart' object has no attribute '{name}'")
217
+
218
+ from .loadfuncs.readdata_old import (
219
+ _assign_var,
220
+ _check_nout,
221
+ _findfiles,
222
+ _init_vardict,
223
+ _load_variables,
224
+ )
225
+ from .loadfuncs.readformat import _check_pathformat, _find_format
226
+ from .loadfuncs.readpart import (
227
+ _compute_offset,
228
+ _inspect_bin,
229
+ _inspect_vtk,
230
+ _store_bin_particles,
231
+ _store_vtk_particles,
232
+ )
233
+ from .toolfuncs.parttools import select, spectrum
pyPLUTO/loadstate.py ADDED
@@ -0,0 +1,68 @@
1
+ """Module that contains the LoadState class."""
2
+
3
+ from dataclasses import dataclass, field
4
+ from typing import Any
5
+
6
+ from numpy.typing import NDArray
7
+
8
+ from pyPLUTO.baseloadstate import BaseLoadState
9
+
10
+
11
+ @dataclass
12
+ class LoadState(BaseLoadState):
13
+ """Class that stores the state of the Load class.
14
+
15
+ Its purpose is to keep track of the current state of the data loading,
16
+ such as the file paths, data arrays, and other properties and update the
17
+ key attributes through all the different classes that handle the data
18
+ loading at runtime.
19
+ """
20
+
21
+ # pylint: disable=too-many-instance-attributes
22
+
23
+ defh: dict = field(init=False, repr=False)
24
+ dim: int = field(init=False)
25
+ dx1: NDArray[Any] = field(init=False)
26
+ dx2: NDArray[Any] = field(init=False)
27
+ dx3: NDArray[Any] = field(init=False)
28
+ full3D: bool = False
29
+ geom: str = field(init=False)
30
+
31
+ gridsize: int = field(init=False)
32
+ gridsize_st1: int = field(init=False)
33
+ gridsize_st2: int = field(init=False)
34
+ gridsize_st3: int = field(init=False)
35
+ level: int = 0
36
+
37
+ nshp: int | tuple[int, ...] = field(init=False)
38
+ nshp_st1: int | tuple[int, ...] | None = field(init=False)
39
+ nshp_st2: tuple[int, ...] | None = field(init=False)
40
+ nshp_st3: tuple[int, ...] | None = field(init=False)
41
+ nx1: int = field(init=False)
42
+ nx2: int = field(init=False)
43
+ nx3: int = field(init=False)
44
+
45
+ plini: dict = field(init=False, repr=False)
46
+
47
+ x1: NDArray[Any] = field(init=False)
48
+ x1c: NDArray[Any] = field(init=False, repr=False)
49
+ x1p: NDArray[Any] = field(init=False, repr=False)
50
+ x1r: NDArray[Any] = field(init=False)
51
+ x1rc: NDArray[Any] = field(init=False, repr=False)
52
+ x1rp: NDArray[Any] = field(init=False, repr=False)
53
+ x1rt: NDArray[Any] = field(init=False, repr=False)
54
+ x1t: NDArray[Any] = field(init=False, repr=False)
55
+
56
+ x2: NDArray[Any] = field(init=False)
57
+ x2c: NDArray[Any] = field(init=False, repr=False)
58
+ x2p: NDArray[Any] = field(init=False, repr=False)
59
+ x2r: NDArray[Any] = field(init=False)
60
+ x2rc: NDArray[Any] = field(init=False, repr=False)
61
+ x2rp: NDArray[Any] = field(init=False, repr=False)
62
+
63
+ x3: NDArray[Any] = field(init=False)
64
+ x3c: NDArray[Any] = field(init=False, repr=False)
65
+ x3r: NDArray[Any] = field(init=False)
66
+ x3rc: NDArray[Any] = field(init=False, repr=False)
67
+ x3rt: NDArray[Any] = field(init=False, repr=False)
68
+ x3t: NDArray[Any] = field(init=False, repr=False)
pyPLUTO/newload.py ADDED
@@ -0,0 +1,81 @@
1
+ """The Load class loads the data (fluid) from the output files."""
2
+
3
+ from typing import Any
4
+
5
+ from pyPLUTO.loadfuncs.initload import InitLoadManager
6
+ from pyPLUTO.loadfuncs.readdefplini import FiledefpliniManager
7
+ from pyPLUTO.loadmixin import LoadMixin
8
+ from pyPLUTO.loadstate import LoadState
9
+ from pyPLUTO.utils.inspector import track_kwargs
10
+
11
+ # mypy: ignore-errors
12
+
13
+ #'''
14
+ # from pathlib import Path
15
+ # from typing import TypedDict, Unpack
16
+ # class MyKwargs(TypedDict, total=False):
17
+ # """TypedDict for keyword arguments."""
18
+
19
+ # code: str
20
+ # endian: str | None
21
+ # level: int
22
+ # multiple: bool
23
+ # nout: str | int | None
24
+ # path: str | Path
25
+ #'''
26
+
27
+
28
+ class Load(LoadMixin):
29
+ """The Load class loads the data (fluid) from the output files.
30
+
31
+ The initialization corresponds to the loading, if wanted, of one or more
32
+ datafiles for the fluid. The data are loaded in a memory mapped numpy
33
+ multidimensional array. Such approach does not load the full data
34
+ until needed. Basic operations (i.e. no numpy) are possible, as well
35
+ as slicing the arrays, without fully loading the data.
36
+ """
37
+
38
+ @track_kwargs
39
+ def __init__(
40
+ self,
41
+ nout: int | str | list[int | str] | None = "last",
42
+ check: bool = True,
43
+ **kwargs: Any,
44
+ ) -> None:
45
+ """Initialize the Load class."""
46
+ kwargs.pop("kwargscheck", check)
47
+
48
+ self.state: LoadState = LoadState()
49
+ self.text: bool | None = kwargs.get("text", self.text)
50
+ self.class_name: str = self.__class__.__name__
51
+ self.full3D: bool = kwargs.get("full3D", self.full3D)
52
+ self.level: int = kwargs.get("level", self.level)
53
+ InitLoadManager(self.state, nout, **kwargs)
54
+ FiledefpliniManager(self.state, **kwargs)
55
+
56
+ if self.text is not False:
57
+ print("Load: Load class initialized.")
58
+
59
+ def __getattr__(self, name: str) -> object:
60
+ """Get the attribute of the Image class."""
61
+ return getattr(self.state, name)
62
+
63
+ def __setattr__(self, name: str, value: object) -> None:
64
+ """Set the attribute of the Image class."""
65
+ if name == "state" or not hasattr(self, "state"):
66
+ return super().__setattr__(name, value)
67
+ return setattr(self.state, name, value)
68
+
69
+ from .loadfuncs.read_files import _read_dat, _read_h5, read_file
70
+ from .loadfuncs.write_files import _write_h5, write_file
71
+ from .toolfuncs.findlines import _check_var, find_contour, find_fieldlines
72
+ from .toolfuncs.fourier import fourier
73
+ from .toolfuncs.nabla import curl, divergence, gradient
74
+ from .toolfuncs.transform import (
75
+ _congrid,
76
+ cartesian_vector,
77
+ mirror,
78
+ reshape_cartesian,
79
+ reshape_uniform,
80
+ slices,
81
+ )
pyPLUTO/pytools.py ADDED
@@ -0,0 +1,145 @@
1
+ """PyPLUTO general tools."""
2
+
3
+ import importlib.util
4
+ import inspect
5
+ import os
6
+ import warnings
7
+ from pathlib import Path
8
+
9
+ import matplotlib.pyplot as plt
10
+
11
+ windows = bool(importlib.util.find_spec("winsound"))
12
+
13
+
14
+ def find_example(name: str) -> Path:
15
+ """Resolve the path to a test problem.
16
+
17
+ The file location is based on the *calling script's*
18
+ location, or fall back to $PLUTO_DIR.
19
+ """
20
+ # Get the path of the calling file
21
+ caller_file = Path(inspect.stack()[1].filename).resolve()
22
+ base_dir = caller_file.parent
23
+
24
+ # Check if the test problem exists in the PyPLUTO Example directory
25
+ local_path = base_dir / "Test_Problems" / name
26
+ if local_path.exists():
27
+ return local_path
28
+
29
+ # Check if the test problem exists in the PLUTO directory
30
+ env_path = os.environ.get("PLUTO_DIR")
31
+ if env_path:
32
+ fallback = Path(env_path) / "Test_Problems" / name
33
+ if fallback.exists():
34
+ return fallback
35
+
36
+ # If the test problem is not found, raise an error
37
+ raise FileNotFoundError(
38
+ f"Test problem '{name}' not found near {base_dir} or in $PLUTO_DIR."
39
+ )
40
+
41
+
42
+ def savefig(**_kwargs: object) -> None:
43
+ """Save the figure created.
44
+
45
+ This function is deprecated and will be removed in future versions.
46
+ Please call savefig from the Image class instead.
47
+ """
48
+ raise NotImplementedError(
49
+ "pyPLUTO.savefig is deprecated.\n"
50
+ "Please call savefig from the Image class instead"
51
+ )
52
+
53
+
54
+ def show(block: bool = True) -> None:
55
+ """Show the figures created.
56
+
57
+ Returns
58
+ -------
59
+ - None
60
+
61
+ Parameters
62
+ ----------
63
+ - block: bool, default True
64
+ If True, the function blocks until the figure is closed.
65
+
66
+ ----
67
+
68
+ Examples
69
+ --------
70
+ - Example #1: Shows the image created
71
+
72
+ >>> pp.show()
73
+
74
+ """
75
+ plt.show(block=block)
76
+
77
+
78
+ def ring(length: float = 0.5, freq: int = 440) -> None:
79
+ """Make a sound for a given length and frequency.
80
+
81
+ It works on Linux, macOS and Windows.
82
+
83
+ Parameters
84
+ ----------
85
+ - length: float, default 0.5
86
+ The length of the sound in seconds.
87
+ - freq: int, default 440
88
+ The frequency of the sound in Hz.
89
+
90
+ Returns
91
+ -------
92
+ - None
93
+
94
+ ----
95
+
96
+ Examples
97
+ --------
98
+ - Example #1: Make a sound with a frequency of 440 Hz and a length
99
+ of 0.5 seconds
100
+
101
+ >>> ring()
102
+
103
+ - Example #2: Make a sound with a frequency of 880 Hz and a length
104
+ of 1 second
105
+
106
+ >>> ring(freq=880)
107
+
108
+ - Example #3: Make a sound with a frequency of 220 Hz and a length
109
+ of 0.2 seconds
110
+
111
+ >>> ring(length=0.2, freq=220)
112
+
113
+ """
114
+ # Check the OS
115
+ if windows is not None:
116
+ try:
117
+ # Check if the 'winsound' package is available on Windows
118
+ winsound = importlib.import_module("winsound")
119
+ if hasattr(winsound, "Beep"):
120
+ winsound.Beep(freq, int(length * 1000))
121
+ except UserWarning:
122
+ # If the 'winsound' package is not available, raise a warning
123
+ text = (
124
+ "pyPLUTO.ring requires the 'winsound' package.\n"
125
+ "Please install it through the command"
126
+ "\n\npip install winsound\n\nand try again."
127
+ )
128
+ warnings.warn(text, UserWarning, stacklevel=2)
129
+ elif os.name == "posix":
130
+ # Check if the 'play' command is available on Linux and macOS
131
+ try:
132
+ os.system(f"play -nq -t alsa synth {length} sine {freq}")
133
+ except UserWarning:
134
+ # If the 'play' command is not available, raise a warning
135
+ text = (
136
+ "pyPLUTO.ring requires the 'play' command from the"
137
+ "'sox' package. \nPlease install it through the command"
138
+ "\n\nsudo apt install sox \n\nand try again."
139
+ )
140
+ warnings.warn(text, UserWarning, stacklevel=2)
141
+
142
+ else:
143
+ # If the OS is not Linux, macOS or Windows, raise a warning
144
+ text = f"pyPLUTO.ring is not implemented for this OS: {os.name}"
145
+ warnings.warn(text, UserWarning, stacklevel=2)