diff --git a/pydrex/cli.html b/pydrex/cli.html index daee4ebc..49d8c3d3 100644 --- a/pydrex/cli.html +++ b/pydrex/cli.html @@ -50,6 +50,24 @@
22class CliTool: +23 """Base class for CLI tools defining the required interface.""" +24 +25 def __call__(self): +26 return NotImplementedError +27 +28 def _get_args(self) -> argparse.Namespace: +29 return NotImplementedError +
Base class for CLI tools defining the required interface.
+32class MeshGenerator(CliTool): +33 """PyDRex script to generate various simple meshes. +34 +35 Only rectangular (2D) meshes are currently supported. +36 +37 """ +38 +39 def __call__(self): +40 try: # This one is dangerous, especially in CI. +41 from pydrex import mesh as _mesh +42 except ImportError: +43 raise _err.MissingDependencyError( +44 "missing optional meshing dependencies." +45 + " Have you installed the package with 'pip install pydrex[mesh]'?" +46 ) +47 +48 args = self._get_args() +49 +50 if args.kind == "rectangle": +51 if args.center is None: +52 center = (0, 0) +53 else: +54 center = [float(s) for s in args.center.split(",")] +55 assert len(center) == 2 +56 +57 width, height = map(float, args.size.split(",")) +58 _mesh.rectangle( +59 args.output[:-4], +60 (args.ref_axes[0], args.ref_axes[1]), +61 center, +62 width, +63 height, +64 args.resolution, +65 ) +66 +67 def _get_args(self) -> argparse.Namespace: +68 description, epilog = self.__doc__.split(os.linesep + os.linesep, 1) +69 parser = argparse.ArgumentParser(description=description, epilog=epilog) +70 parser.add_argument("size", help="width,height[,depth] of the mesh") +71 parser.add_argument( +72 "resolution", help="base resolution of the mesh (edge length hint for gmsh)" +73 ) +74 parser.add_argument("output", help="output file (.msh)") +75 parser.add_argument( +76 "-c", +77 "--center", +78 help="center of the mesh as 2 or 3 comma-separated coordinates. default: (0, 0[, 0])", +79 default=None, +80 ) +81 parser.add_argument( +82 "-a", +83 "--ref-axes", +84 help=( +85 "two letters from {'x', 'y', 'z'} that specify" +86 + " the horizontal and vertical axes of the mesh" +87 ), +88 default="xz", +89 ) +90 parser.add_argument( +91 "-k", "--kind", help="kind of mesh, e.g. 'rectangle'", default="rectangle" +92 ) +93 return parser.parse_args() +
PyDRex script to generate various simple meshes.
+ +Only rectangular (2D) meshes are currently supported.
+96class H5partExtractor(CliTool): + 97 """PyDRex script to extract raw CPO data from Fluidity .h5part files. + 98 + 99 Fluidity saves data stored on model `particles` to an `.h5part` file. +100 This script converts that file to canonical serialisation formats: +101 - a `.npz` file containing the raw CPO orientations and (surrogate) grain sizes +102 - an `.scsv` file containing the pathline positions and accumulated strain +103 +104 It is assumed that CPO data is stored in keys called 'CPO_<N>' in the .h5part +105 data, where `<N>` is an integer in the range 1—`n_grains`. The accumulated strain is +106 read from the attribute `CPO_<S>` where S=`ngrains`+1. Particle positions are read +107 from the attributes `x`, `y`, and `z`. +108 +109 At the moment, dynamic changes in fabric or phase are not supported. +110 +111 """ +112 +113 def __call__(self): +114 args = self._get_args() +115 _io.extract_h5part( +116 args.input, args.phase, args.fabric, args.ngrains, args.output +117 ) +118 +119 def _get_args(self) -> argparse.Namespace: +120 description, epilog = self.__doc__.split(os.linesep + os.linesep, 1) +121 parser = argparse.ArgumentParser(description=description, epilog=epilog) +122 parser.add_argument("input", help="input file (.h5part)") +123 parser.add_argument( +124 "-p", +125 "--phase", +126 help="type of `pydrex.MineralPhase` (as an ordinal number); 0 by default", +127 default=0, +128 ) +129 parser.add_argument( +130 "-f", +131 "--fabric", +132 type=int, +133 help="type of `pydrex.MineralFabric` (as an ordinal number); 0 by default", +134 default=0, +135 ) +136 parser.add_argument( +137 "-n", +138 "--ngrains", +139 help="number of grains used in the Fluidity simulation", +140 type=int, +141 required=True, +142 ) +143 parser.add_argument( +144 "-o", +145 "--output", +146 help="filename for the output NPZ file (stem also used for the .scsv)", +147 required=True, +148 ) +149 return parser.parse_args() +
PyDRex script to extract raw CPO data from Fluidity .h5part files.
+ +Fluidity saves data stored on model particles
to an .h5part
file.
+This script converts that file to canonical serialisation formats:
.npz
file containing the raw CPO orientations and (surrogate) grain sizes.scsv
file containing the pathline positions and accumulated strainIt is assumed that CPO data is stored in keys called 'CPO_<N>
is an integer in the range 1—n_grains
. The accumulated strain is
+read from the attribute CPO_<S>
where S=ngrains
+1. Particle positions are read
+from the attributes x
, y
, and z
.
At the moment, dynamic changes in fabric or phase are not supported.
+23@dataclass -24class NPZFileInspector: -25 """PyDRex script to show information about serialized CPO data. -26 -27 Lists the keys that should be used for the `postfix` in `pydrex.Mineral.load` and -28 `pydrex.Mineral.from_file`. -29 -30 """ -31 -32 def __call__(self): -33 args = self._get_args() -34 with ZipFile(args.input) as npz: -35 names = npz.namelist() -36 print("NPZ file with keys:") -37 for name in names: -38 if not ( -39 name.startswith("meta") -40 or name.startswith("fractions") -41 or name.startswith("orientations") -42 ): -43 _log.warning(f"found unknown NPZ key '{name}' in '{args.input}'") -44 print(f" - {name}") -45 -46 def _get_args(self) -> argparse.Namespace: -47 description, epilog = self.__doc__.split(os.linesep + os.linesep, 1) -48 parser = argparse.ArgumentParser(description=description, epilog=epilog) -49 parser.add_argument("input", help="input file (.npz)") -50 return parser.parse_args() +@@ -337,138 +684,136 @@152class NPZFileInspector(CliTool): +153 """PyDRex script to show information about serialized CPO data. +154 +155 Lists the keys that should be used for the `postfix` in `pydrex.Mineral.load` and +156 `pydrex.Mineral.from_file`. +157 +158 """ +159 +160 def __call__(self): +161 args = self._get_args() +162 with ZipFile(args.input) as npz: +163 names = npz.namelist() +164 print("NPZ file with keys:") +165 for name in names: +166 if not ( +167 name.startswith("meta") +168 or name.startswith("fractions") +169 or name.startswith("orientations") +170 ): +171 _log.warning(f"found unknown NPZ key '{name}' in '{args.input}'") +172 print(f" - {name}") +173 +174 def _get_args(self) -> argparse.Namespace: +175 description, epilog = self.__doc__.split(os.linesep + os.linesep, 1) +176 parser = argparse.ArgumentParser(description=description, epilog=epilog) +177 parser.add_argument("input", help="input file (.npz)") +178 return parser.parse_args()
- @@ -221,6 +269,78 @@53@dataclass - 54class PoleFigureVisualiser: - 55 """PyDRex script to plot pole figures of serialized CPO data. - 56 - 57 Produces [100], [010] and [001] pole figures for serialized `pydrex.Mineral`s. - 58 If the range of indices is not specified, - 59 a maximum of 25 of each pole figure will be produced by default. - 60 - 61 """ - 62 - 63 def __call__(self): - 64 try: - 65 args = self._get_args() - 66 if args.range is None: - 67 i_range = None - 68 else: - 69 start, stop_ex, step = (int(s) for s in args.range.split(":")) - 70 # Make command line start:stop:step stop-inclusive, it's more intuitive. - 71 i_range = range(start, stop_ex + step, step) - 72 - 73 density_kwargs = {"kernel": args.kernel} - 74 if args.smoothing is not None: - 75 density_kwargs["σ"] = args.smoothing - 76 - 77 mineral = _minerals.Mineral.from_file(args.input, postfix=args.postfix) - 78 if i_range is None: - 79 i_range = range(0, len(mineral.orientations)) - 80 if len(i_range) > 25: - 81 _log.warning( - 82 "truncating to 25 timesteps (out of %s total)", len(i_range) - 83 ) - 84 i_range = range(0, 25) - 85 - 86 orientations_resampled, _ = _stats.resample_orientations( - 87 mineral.orientations[i_range.start : i_range.stop : i_range.step], - 88 mineral.fractions[i_range.start : i_range.stop : i_range.step], - 89 ) - 90 if args.scsv is None: - 91 strains = None - 92 else: - 93 strains = _io.read_scsv(args.scsv).strain[ - 94 i_range.start : i_range.stop : i_range.step - 95 ] - 96 _vis.polefigures( - 97 orientations_resampled, - 98 ref_axes=args.ref_axes, - 99 i_range=i_range, -100 density=args.density, -101 savefile=args.out, -102 strains=strains, -103 **density_kwargs, -104 ) -105 except (argparse.ArgumentError, ValueError, _err.Error) as e: -106 _log.error(str(e)) -107 -108 def _get_args(self) -> argparse.Namespace: -109 description, epilog = self.__doc__.split(os.linesep + os.linesep, 1) -110 parser = argparse.ArgumentParser(description=description, epilog=epilog) -111 parser.add_argument("input", help="input file (.npz)") -112 parser.add_argument( -113 "-r", -114 "--range", -115 help="range of strain indices to be plotted, in the format start:stop:step", -116 default=None, -117 ) -118 parser.add_argument( -119 "-f", -120 "--scsv", -121 help=( -122 "path to SCSV file with a column named 'strain'" -123 + " that lists shear strain percentages for each strain index" -124 ), -125 default=None, -126 ) -127 parser.add_argument( -128 "-p", -129 "--postfix", -130 help=( -131 "postfix of the mineral to load," -132 + " required if the input file contains data for multiple minerals" -133 ), -134 default=None, -135 ) -136 parser.add_argument( -137 "-d", -138 "--density", -139 help="toggle contouring of pole figures using point density estimation", -140 default=False, -141 action="store_true", -142 ) -143 parser.add_argument( -144 "-k", -145 "--kernel", -146 help=( -147 "kernel function for point density estimation, one of:" -148 + f" {list(_stats.SPHERICAL_COUNTING_KERNELS.keys())}" -149 ), -150 default="linear_inverse_kamb", -151 ) -152 parser.add_argument( -153 "-s", -154 "--smoothing", -155 help="smoothing parameter for Kamb type density estimation kernels", -156 default=None, -157 type=float, -158 metavar="σ", -159 ) -160 parser.add_argument( -161 "-a", -162 "--ref-axes", -163 help=( -164 "two letters from {'x', 'y', 'z'} that specify" -165 + " the horizontal and vertical axes of the pole figures" -166 ), -167 default="xz", -168 ) -169 parser.add_argument( -170 "-o", -171 "--out", -172 help="name of the output file, with either .png or .pdf extension", -173 default="polefigures.png", -174 ) -175 return parser.parse_args() +@@ -484,7 +829,8 @@181class PoleFigureVisualiser(CliTool): +182 """PyDRex script to plot pole figures of serialized CPO data. +183 +184 Produces [100], [010] and [001] pole figures for serialized `pydrex.Mineral`s. +185 If the range of indices is not specified, +186 a maximum of 25 of each pole figure will be produced by default. +187 +188 """ +189 +190 def __call__(self): +191 try: +192 args = self._get_args() +193 if args.range is None: +194 i_range = None +195 else: +196 start, stop_ex, step = (int(s) for s in args.range.split(":")) +197 # Make command line start:stop:step stop-inclusive, it's more intuitive. +198 i_range = range(start, stop_ex + step, step) +199 +200 density_kwargs = {"kernel": args.kernel} +201 if args.smoothing is not None: +202 density_kwargs["σ"] = args.smoothing +203 +204 mineral = _minerals.Mineral.from_file(args.input, postfix=args.postfix) +205 if i_range is None: +206 i_range = range(0, len(mineral.orientations)) +207 if len(i_range) > 25: +208 _log.warning( +209 "truncating to 25 timesteps (out of %s total)", len(i_range) +210 ) +211 i_range = range(0, 25) +212 +213 orientations_resampled, _ = _stats.resample_orientations( +214 mineral.orientations[i_range.start : i_range.stop : i_range.step], +215 mineral.fractions[i_range.start : i_range.stop : i_range.step], +216 ) +217 if args.scsv is None: +218 strains = None +219 else: +220 strains = _io.read_scsv(args.scsv).strain[ +221 i_range.start : i_range.stop : i_range.step +222 ] +223 _vis.polefigures( +224 orientations_resampled, +225 ref_axes=args.ref_axes, +226 i_range=i_range, +227 density=args.density, +228 savefile=args.out, +229 strains=strains, +230 **density_kwargs, +231 ) +232 except (argparse.ArgumentError, ValueError, _err.Error) as e: +233 _log.error(str(e)) +234 +235 def _get_args(self) -> argparse.Namespace: +236 description, epilog = self.__doc__.split(os.linesep + os.linesep, 1) +237 parser = argparse.ArgumentParser(description=description, epilog=epilog) +238 parser.add_argument("input", help="input file (.npz)") +239 parser.add_argument( +240 "-r", +241 "--range", +242 help="range of strain indices to be plotted, in the format start:stop:step", +243 default=None, +244 ) +245 parser.add_argument( +246 "-f", +247 "--scsv", +248 help=( +249 "path to SCSV file with a column named 'strain'" +250 + " that lists shear strain percentages for each strain index" +251 ), +252 default=None, +253 ) +254 parser.add_argument( +255 "-p", +256 "--postfix", +257 help=( +258 "postfix of the mineral to load," +259 + " required if the input file contains data for multiple minerals" +260 ), +261 default=None, +262 ) +263 parser.add_argument( +264 "-d", +265 "--density", +266 help="toggle contouring of pole figures using point density estimation", +267 default=False, +268 action="store_true", +269 ) +270 parser.add_argument( +271 "-k", +272 "--kernel", +273 help=( +274 "kernel function for point density estimation, one of:" +275 + f" {list(_stats.SPHERICAL_COUNTING_KERNELS.keys())}" +276 ), +277 default="linear_inverse_kamb", +278 ) +279 parser.add_argument( +280 "-s", +281 "--smoothing", +282 help="smoothing parameter for Kamb type density estimation kernels", +283 default=None, +284 type=float, +285 metavar="σ", +286 ) +287 parser.add_argument( +288 "-a", +289 "--ref-axes", +290 help=( +291 "two letters from {'x', 'y', 'z'} that specify" +292 + " the horizontal and vertical axes of the pole figures" +293 ), +294 default="xz", +295 ) +296 parser.add_argument( +297 "-o", +298 "--out", +299 help="name of the output file, with either .png or .pdf extension", +300 default="polefigures.png", +301 ) +302 return parser.parse_args()7 """Base class for exceptions in PyDRex.""" 8 9 -10class ConfigError(Error): -11 """Exception raised for errors in the input configuration. +10class MissingDependencyError(Error): +11 """Exception raised when optional dependencies are missing. 12 13 Attributes: 14 message — explanation of the error @@ -150,8 +174,8 @@
CLI_HANDLERS = -CLI_HANDLERS(pole_figure_visualiser=PoleFigureVisualiser(), npz_file_inspector=NPZFileInspector()) + + CLI_HANDLERS(pole_figure_visualiser=<PoleFigureVisualiser object>, npz_file_inspector=<NPZFileInspector object>, mesh_generator=<MeshGenerator object>, h5part_extractor=<H5partExtractor object>)diff --git a/pydrex/core.html b/pydrex/core.html index aa7822ba..33a1b3cc 100644 --- a/pydrex/core.html +++ b/pydrex/core.html @@ -678,6 +678,7 @@Inherited Members
- to_bytes
- from_bytes
- as_integer_ratio
+- is_integer
- real
- imag
- numerator
@@ -777,6 +778,7 @@Inherited Members
- to_bytes
- from_bytes
- as_integer_ratio
+- is_integer
- real
- imag
- numerator
@@ -919,6 +921,7 @@Inherited Members
- to_bytes
- from_bytes
- as_integer_ratio
+- is_integer
- real
- imag
- numerator
diff --git a/pydrex/exceptions.html b/pydrex/exceptions.html index 4489d0a6..2bfcddae 100644 --- a/pydrex/exceptions.html +++ b/pydrex/exceptions.html @@ -55,6 +55,18 @@API Documentation
+ +
- + MissingDependencyError +
+
+- + MissingDependencyError +
+- + message +
+- ConfigError @@ -104,6 +116,18 @@
+API Documentation
- + ModelContextError +
@@ -138,8 +162,8 @@+
+ +- + ModelContextError +
+- + message +
+
19 self.message = message 20 21 -22class MeshError(Error): -23 """Exception raised for errors in the input mesh. +22class ConfigError(Error): +23 """Exception raised for errors in the input configuration. 24 25 Attributes: 26 message — explanation of the error @@ -162,8 +186,8 @@
31 self.message = message 32 33 -34class IterationError(Error): -35 """Exception raised for errors in numerical iteration schemes. +34class MeshError(Error): +35 """Exception raised for errors in the input mesh. 36 37 Attributes: 38 message — explanation of the error @@ -171,20 +195,44 @@
40 """ 41 42 def __init__(self, message): # pylint: disable=super-init-not-called -43 # TODO: Add data attribute? Timestep? -44 self.message = message +43 self.message = message +44 45 -46 -47class SCSVError(Error): -48 """Exception raised for errors in SCSV file I/O. -49 -50 Attributes: -51 - message — explanation of the error -52 -53 """ -54 -55 def __init__(self, message): # pylint: disable=super-init-not-called +46class IterationError(Error): +47 """Exception raised for errors in numerical iteration schemes. +48 +49 Attributes: +50 message — explanation of the error +51 +52 """ +53 +54 def __init__(self, message): # pylint: disable=super-init-not-called +55 # TODO: Add data attribute? Timestep? 56 self.message = message +57 +58 +59class SCSVError(Error): +60 """Exception raised for errors in SCSV file I/O. +61 +62 Attributes: +63 - message — explanation of the error +64 +65 """ +66 +67 def __init__(self, message): # pylint: disable=super-init-not-called +68 self.message = message +69 +70 +71class ModelContextError(Error): +72 """Exception raised for errors in `mesh.Model` context state. +73 +74 Attributes: +75 message — explanation of the error +76 +77 """ +78 +79 def __init__(self, message): # pylint: disable=super-init-not-called +80 self.message = message
Inherited Members
- add_note
- args
+
11class MissingDependencyError(Error): +12 """Exception raised when optional dependencies are missing. +13 +14 Attributes: +15 message — explanation of the error +16 +17 """ +18 +19 def __init__(self, message): # pylint: disable=super-init-not-called +20 self.message = message +
Exception raised when optional dependencies are missing.
+ +Attributes: + message — explanation of the error
+11class ConfigError(Error): -12 """Exception raised for errors in the input configuration. -13 -14 Attributes: -15 message — explanation of the error -16 -17 """ -18 -19 def __init__(self, message): # pylint: disable=super-init-not-called -20 self.message = message +@@ -266,8 +386,8 @@23class ConfigError(Error): +24 """Exception raised for errors in the input configuration. +25 +26 Attributes: +27 message — explanation of the error +28 +29 """ +30 +31 def __init__(self, message): # pylint: disable=super-init-not-called +32 self.message = messageInherited Members
19 def __init__(self, message): # pylint: disable=super-init-not-called -20 self.message = message + @@ -308,16 +428,16 @@Inherited Members
23class MeshError(Error): -24 """Exception raised for errors in the input mesh. -25 -26 Attributes: -27 message — explanation of the error -28 -29 """ -30 -31 def __init__(self, message): # pylint: disable=super-init-not-called -32 self.message = message +@@ -338,8 +458,8 @@35class MeshError(Error): +36 """Exception raised for errors in the input mesh. +37 +38 Attributes: +39 message — explanation of the error +40 +41 """ +42 +43 def __init__(self, message): # pylint: disable=super-init-not-called +44 self.message = messageInherited Members
31 def __init__(self, message): # pylint: disable=super-init-not-called -32 self.message = message + @@ -380,17 +500,17 @@Inherited Members
35class IterationError(Error): -36 """Exception raised for errors in numerical iteration schemes. -37 -38 Attributes: -39 message — explanation of the error -40 -41 """ -42 -43 def __init__(self, message): # pylint: disable=super-init-not-called -44 # TODO: Add data attribute? Timestep? -45 self.message = message +@@ -411,9 +531,9 @@47class IterationError(Error): +48 """Exception raised for errors in numerical iteration schemes. +49 +50 Attributes: +51 message — explanation of the error +52 +53 """ +54 +55 def __init__(self, message): # pylint: disable=super-init-not-called +56 # TODO: Add data attribute? Timestep? +57 self.message = messageInherited Members
43 def __init__(self, message): # pylint: disable=super-init-not-called -44 # TODO: Add data attribute? Timestep? -45 self.message = message +@@ -454,16 +574,16 @@55 def __init__(self, message): # pylint: disable=super-init-not-called +56 # TODO: Add data attribute? Timestep? +57 self.message = messageInherited Members
48class SCSVError(Error): -49 """Exception raised for errors in SCSV file I/O. -50 -51 Attributes: -52 - message — explanation of the error -53 -54 """ -55 -56 def __init__(self, message): # pylint: disable=super-init-not-called -57 self.message = message +@@ -487,8 +607,8 @@60class SCSVError(Error): +61 """Exception raised for errors in SCSV file I/O. +62 +63 Attributes: +64 - message — explanation of the error +65 +66 """ +67 +68 def __init__(self, message): # pylint: disable=super-init-not-called +69 self.message = messageInherited Members
72class ModelContextError(Error): +73 """Exception raised for errors in `mesh.Model` context state. +74 +75 Attributes: +76 message — explanation of the error +77 +78 """ +79 +80 def __init__(self, message): # pylint: disable=super-init-not-called +81 self.message = message +
Exception raised for errors in mesh.Model
context state.
Attributes: + message — explanation of the error
+