tests.test_corner_flow_2d

PyDRex: 2D corner flow tests.

  1"""> PyDRex: 2D corner flow tests."""
  2
  3import contextlib as cl
  4import functools as ft
  5import pathlib as pl
  6from time import perf_counter
  7
  8import numpy as np
  9import pytest
 10
 11from pydrex import core as _core
 12from pydrex import diagnostics as _diagnostics
 13from pydrex import geometry as _geo
 14from pydrex import io as _io
 15from pydrex import logger as _log
 16from pydrex import minerals as _minerals
 17from pydrex import pathlines as _path
 18from pydrex import stats as _stats
 19from pydrex import utils as _utils
 20from pydrex import velocity as _velocity
 21from pydrex import visualisation as _vis
 22
 23Pool, HAS_RAY = _utils.import_proc_pool()
 24
 25# Subdirectory of `outdir` used to store outputs from these tests.
 26SUBDIR = "2d_cornerflow"
 27
 28
 29class TestOlivineA:
 30    """Tests for pure A-type olivine polycrystals in 2D corner flows."""
 31
 32    class_id = "corner_olivineA"
 33
 34    @classmethod
 35    def run(
 36        cls,
 37        params,
 38        seed,
 39        get_velocity,
 40        get_velocity_gradient,
 41        min_coords,
 42        max_coords,
 43        max_strain,
 44        n_timesteps,
 45        final_location,
 46    ):
 47        """Run 2D corner flow A-type olivine simulation."""
 48        mineral = _minerals.Mineral(
 49            _core.MineralPhase.olivine,
 50            _core.MineralFabric.olivine_A,
 51            _core.DeformationRegime.matrix_dislocation,
 52            n_grains=params["number_of_grains"],
 53            seed=seed,
 54        )
 55        deformation_gradient = np.eye(3)
 56        timestamps, get_position = _path.get_pathline(
 57            final_location,
 58            get_velocity,
 59            get_velocity_gradient,
 60            min_coords,
 61            max_coords,
 62            max_strain,
 63            regular_steps=n_timesteps,
 64        )
 65        positions = [get_position(t) for t in timestamps]
 66        velocity_gradients = [
 67            get_velocity_gradient(np.nan, np.asarray(x)) for x in positions
 68        ]
 69        strains = np.empty_like(timestamps)
 70        strains[0] = 0
 71        for t, time in enumerate(timestamps[:-1], start=1):
 72            strains[t] = strains[t - 1] + (
 73                _utils.strain_increment(timestamps[t] - time, velocity_gradients[t])
 74            )
 75            _log.info(
 76                "final location = %s; step %d/%d (ε = %.2f)",
 77                final_location.ravel(),
 78                t,
 79                len(timestamps) - 1,
 80                strains[t],
 81            )
 82
 83            deformation_gradient = mineral.update_orientations(
 84                params,
 85                deformation_gradient,
 86                get_velocity_gradient,
 87                pathline=(time, timestamps[t], get_position),
 88            )
 89        return timestamps, positions, strains, mineral, deformation_gradient
 90
 91    @pytest.mark.slow
 92    def test_steady4(self, outdir, seed, ncpus):
 93        """Test CPO evolution in steady 2D corner flow along 4 pathlines.
 94
 95        Initial condition: random orientations and uniform volumes in all `Mineral`s.
 96
 97        Plate velocity: 2 cm/yr
 98
 99        .. note::
100            This example takes about 11 CPU hours to run and uses around 60GB of RAM.
101            It is recommended to only use `ncpus=4` which matches the number of
102            pathlines, because higher numbers can lead to redundant cross-core
103            communication.
104
105        """
106        # Plate speed (half spreading rate), convert cm/yr to m/s.
107        plate_speed = 2.0 / (100.0 * 365.0 * 86400.0)
108        domain_height = 2.0e5  # Represents the depth of olivine-spinel transition.
109        domain_width = 1.0e6
110        params = _core.DefaultParams().as_dict()
111        params["number_of_grains"] = 5000
112        n_timesteps = 50  # Number of places along the pathline to compute CPO.
113        get_velocity, get_velocity_gradient = _velocity.corner_2d("X", "Z", plate_speed)
114        # Z-values at the end of each pathline.
115        z_ends = list(map(lambda x: x * domain_height, (-0.1, -0.3, -0.54, -0.78)))
116
117        # Optional plotting and logging setup.
118        optional_logging = cl.nullcontext()
119        if outdir is not None:
120            out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_prescribed"
121            optional_logging = _io.logfile_enable(f"{out_basepath}.log")
122            npzpath = pl.Path(f"{out_basepath}.npz")
123            labels = []
124            angles = []
125            indices = []
126            paths = []
127            path_strains = []
128            directions = []
129            max_grainsizes = []
130            timestamps = []
131
132        _run = ft.partial(
133            self.run,
134            params,
135            seed,
136            get_velocity,
137            get_velocity_gradient,
138            np.array([0.0, 0.0, -domain_height]),
139            np.array([domain_width, 0.0, 0.0]),
140            10,
141            n_timesteps,
142        )
143
144        final_locations = [np.array([domain_width, 0.0, z_exit]) for z_exit in z_ends]
145        with optional_logging:
146            _begin = perf_counter()
147            with Pool(processes=ncpus) as pool:
148                for i, out in enumerate(pool.imap(_run, final_locations)):
149                    times, positions, strains, mineral, deformation_gradient = out
150                    _log.info("final deformation gradient:\n%s", deformation_gradient)
151                    z_exit = z_ends[i]
152                    if outdir is not None:
153                        mineral.save(npzpath, _io.stringify(z_exit))
154
155                    misorient_angles = np.zeros(n_timesteps)
156                    bingham_vectors = np.zeros((n_timesteps, 3))
157                    orientations_resampled, f_resampled = _stats.resample_orientations(
158                        mineral.orientations, mineral.fractions, seed=seed
159                    )
160                    misorient_indices = _diagnostics.misorientation_indices(
161                        orientations_resampled,
162                        _geo.LatticeSystem.orthorhombic,
163                        pool=pool,
164                    )
165                    for idx, matrices in enumerate(orientations_resampled):
166                        direction_mean = _diagnostics.bingham_average(
167                            matrices,
168                            axis=_minerals.OLIVINE_PRIMARY_AXIS[mineral.fabric],
169                        )
170                        misorient_angles[idx] = _diagnostics.smallest_angle(
171                            direction_mean,
172                            np.asarray([1, 0, 0], dtype=np.float64),
173                        )
174                        bingham_vectors[idx] = direction_mean
175
176                    _log.debug("Total walltime: %s", perf_counter() - _begin)
177
178                    if outdir is not None:
179                        labels.append(rf"$z_{{f}}$ = {z_exit/1e3:.1f} km")
180                        angles.append(misorient_angles)
181                        indices.append(misorient_indices)
182                        paths.append(positions)
183                        max_grainsizes.append(
184                            np.asarray([np.max(f) for f in f_resampled])
185                        )
186                        path_strains.append(strains)
187                        timestamps.append(times)
188                        directions.append(bingham_vectors)
189
190        if outdir is not None:
191            np.savez(
192                f"{out_basepath}_path.npz",
193                strains_1=path_strains[0],
194                strains_2=path_strains[1],
195                strains_3=path_strains[2],
196                strains_4=path_strains[3],
197                paths_1=np.asarray(paths[0]),
198                paths_2=np.asarray(paths[1]),
199                paths_3=np.asarray(paths[2]),
200                paths_4=np.asarray(paths[3]),
201            )
202            np.savez(
203                f"{out_basepath}_strength.npz",
204                strength_1=indices[0],
205                strength_2=indices[1],
206                strength_3=indices[2],
207                strength_4=indices[3],
208            )
209            np.savez(
210                f"{out_basepath}_angles.npz",
211                angles_1=angles[0],
212                angles_2=angles[1],
213                angles_3=angles[2],
214                angles_4=angles[3],
215            )
216            markers = ("s", "o", "v", "*")
217            cmaps = ["cmc.batlow_r"] * len(markers)
218            fig_domain = _vis.figure(figsize=(10, 3))
219            ax_domain = fig_domain.add_subplot()
220            for i, z_exit in enumerate(z_ends):
221                if i == 0:
222                    resolution = [25, 5]
223                else:
224                    resolution = None
225                _vis.pathline_box2d(
226                    ax_domain,
227                    get_velocity,
228                    "xz",
229                    path_strains[i],
230                    paths[i],
231                    markers[i],
232                    [0, -domain_height],
233                    [domain_width + 1e5, 0],
234                    resolution,
235                    cmap=cmaps[i],
236                    cpo_vectors=directions[i],
237                    cpo_strengths=indices[i],
238                    scale=25,
239                    scale_units="width",
240                )
241
242            fig_strength, ax_strength, colors = _vis.strengths(
243                None,
244                path_strains,
245                indices,
246                "CPO strength (M-index)",
247                markers,
248                labels,
249                colors=path_strains,
250                cmaps=cmaps,
251            )
252            fig_alignment, ax_alignment, colors = _vis.alignment(
253                None,
254                path_strains,
255                angles,
256                markers,
257                labels,
258                colors=path_strains,
259                cmaps=cmaps,
260            )
261
262            fig_domain.savefig(_io.resolve_path(f"{out_basepath}_path.pdf"))
263            fig_strength.savefig(_io.resolve_path(f"{out_basepath}_strength.pdf"))
264            fig_alignment.savefig(_io.resolve_path(f"{out_basepath}_angles.pdf"))
SUBDIR = '2d_cornerflow'
class TestOlivineA:
 30class TestOlivineA:
 31    """Tests for pure A-type olivine polycrystals in 2D corner flows."""
 32
 33    class_id = "corner_olivineA"
 34
 35    @classmethod
 36    def run(
 37        cls,
 38        params,
 39        seed,
 40        get_velocity,
 41        get_velocity_gradient,
 42        min_coords,
 43        max_coords,
 44        max_strain,
 45        n_timesteps,
 46        final_location,
 47    ):
 48        """Run 2D corner flow A-type olivine simulation."""
 49        mineral = _minerals.Mineral(
 50            _core.MineralPhase.olivine,
 51            _core.MineralFabric.olivine_A,
 52            _core.DeformationRegime.matrix_dislocation,
 53            n_grains=params["number_of_grains"],
 54            seed=seed,
 55        )
 56        deformation_gradient = np.eye(3)
 57        timestamps, get_position = _path.get_pathline(
 58            final_location,
 59            get_velocity,
 60            get_velocity_gradient,
 61            min_coords,
 62            max_coords,
 63            max_strain,
 64            regular_steps=n_timesteps,
 65        )
 66        positions = [get_position(t) for t in timestamps]
 67        velocity_gradients = [
 68            get_velocity_gradient(np.nan, np.asarray(x)) for x in positions
 69        ]
 70        strains = np.empty_like(timestamps)
 71        strains[0] = 0
 72        for t, time in enumerate(timestamps[:-1], start=1):
 73            strains[t] = strains[t - 1] + (
 74                _utils.strain_increment(timestamps[t] - time, velocity_gradients[t])
 75            )
 76            _log.info(
 77                "final location = %s; step %d/%d (ε = %.2f)",
 78                final_location.ravel(),
 79                t,
 80                len(timestamps) - 1,
 81                strains[t],
 82            )
 83
 84            deformation_gradient = mineral.update_orientations(
 85                params,
 86                deformation_gradient,
 87                get_velocity_gradient,
 88                pathline=(time, timestamps[t], get_position),
 89            )
 90        return timestamps, positions, strains, mineral, deformation_gradient
 91
 92    @pytest.mark.slow
 93    def test_steady4(self, outdir, seed, ncpus):
 94        """Test CPO evolution in steady 2D corner flow along 4 pathlines.
 95
 96        Initial condition: random orientations and uniform volumes in all `Mineral`s.
 97
 98        Plate velocity: 2 cm/yr
 99
100        .. note::
101            This example takes about 11 CPU hours to run and uses around 60GB of RAM.
102            It is recommended to only use `ncpus=4` which matches the number of
103            pathlines, because higher numbers can lead to redundant cross-core
104            communication.
105
106        """
107        # Plate speed (half spreading rate), convert cm/yr to m/s.
108        plate_speed = 2.0 / (100.0 * 365.0 * 86400.0)
109        domain_height = 2.0e5  # Represents the depth of olivine-spinel transition.
110        domain_width = 1.0e6
111        params = _core.DefaultParams().as_dict()
112        params["number_of_grains"] = 5000
113        n_timesteps = 50  # Number of places along the pathline to compute CPO.
114        get_velocity, get_velocity_gradient = _velocity.corner_2d("X", "Z", plate_speed)
115        # Z-values at the end of each pathline.
116        z_ends = list(map(lambda x: x * domain_height, (-0.1, -0.3, -0.54, -0.78)))
117
118        # Optional plotting and logging setup.
119        optional_logging = cl.nullcontext()
120        if outdir is not None:
121            out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_prescribed"
122            optional_logging = _io.logfile_enable(f"{out_basepath}.log")
123            npzpath = pl.Path(f"{out_basepath}.npz")
124            labels = []
125            angles = []
126            indices = []
127            paths = []
128            path_strains = []
129            directions = []
130            max_grainsizes = []
131            timestamps = []
132
133        _run = ft.partial(
134            self.run,
135            params,
136            seed,
137            get_velocity,
138            get_velocity_gradient,
139            np.array([0.0, 0.0, -domain_height]),
140            np.array([domain_width, 0.0, 0.0]),
141            10,
142            n_timesteps,
143        )
144
145        final_locations = [np.array([domain_width, 0.0, z_exit]) for z_exit in z_ends]
146        with optional_logging:
147            _begin = perf_counter()
148            with Pool(processes=ncpus) as pool:
149                for i, out in enumerate(pool.imap(_run, final_locations)):
150                    times, positions, strains, mineral, deformation_gradient = out
151                    _log.info("final deformation gradient:\n%s", deformation_gradient)
152                    z_exit = z_ends[i]
153                    if outdir is not None:
154                        mineral.save(npzpath, _io.stringify(z_exit))
155
156                    misorient_angles = np.zeros(n_timesteps)
157                    bingham_vectors = np.zeros((n_timesteps, 3))
158                    orientations_resampled, f_resampled = _stats.resample_orientations(
159                        mineral.orientations, mineral.fractions, seed=seed
160                    )
161                    misorient_indices = _diagnostics.misorientation_indices(
162                        orientations_resampled,
163                        _geo.LatticeSystem.orthorhombic,
164                        pool=pool,
165                    )
166                    for idx, matrices in enumerate(orientations_resampled):
167                        direction_mean = _diagnostics.bingham_average(
168                            matrices,
169                            axis=_minerals.OLIVINE_PRIMARY_AXIS[mineral.fabric],
170                        )
171                        misorient_angles[idx] = _diagnostics.smallest_angle(
172                            direction_mean,
173                            np.asarray([1, 0, 0], dtype=np.float64),
174                        )
175                        bingham_vectors[idx] = direction_mean
176
177                    _log.debug("Total walltime: %s", perf_counter() - _begin)
178
179                    if outdir is not None:
180                        labels.append(rf"$z_{{f}}$ = {z_exit/1e3:.1f} km")
181                        angles.append(misorient_angles)
182                        indices.append(misorient_indices)
183                        paths.append(positions)
184                        max_grainsizes.append(
185                            np.asarray([np.max(f) for f in f_resampled])
186                        )
187                        path_strains.append(strains)
188                        timestamps.append(times)
189                        directions.append(bingham_vectors)
190
191        if outdir is not None:
192            np.savez(
193                f"{out_basepath}_path.npz",
194                strains_1=path_strains[0],
195                strains_2=path_strains[1],
196                strains_3=path_strains[2],
197                strains_4=path_strains[3],
198                paths_1=np.asarray(paths[0]),
199                paths_2=np.asarray(paths[1]),
200                paths_3=np.asarray(paths[2]),
201                paths_4=np.asarray(paths[3]),
202            )
203            np.savez(
204                f"{out_basepath}_strength.npz",
205                strength_1=indices[0],
206                strength_2=indices[1],
207                strength_3=indices[2],
208                strength_4=indices[3],
209            )
210            np.savez(
211                f"{out_basepath}_angles.npz",
212                angles_1=angles[0],
213                angles_2=angles[1],
214                angles_3=angles[2],
215                angles_4=angles[3],
216            )
217            markers = ("s", "o", "v", "*")
218            cmaps = ["cmc.batlow_r"] * len(markers)
219            fig_domain = _vis.figure(figsize=(10, 3))
220            ax_domain = fig_domain.add_subplot()
221            for i, z_exit in enumerate(z_ends):
222                if i == 0:
223                    resolution = [25, 5]
224                else:
225                    resolution = None
226                _vis.pathline_box2d(
227                    ax_domain,
228                    get_velocity,
229                    "xz",
230                    path_strains[i],
231                    paths[i],
232                    markers[i],
233                    [0, -domain_height],
234                    [domain_width + 1e5, 0],
235                    resolution,
236                    cmap=cmaps[i],
237                    cpo_vectors=directions[i],
238                    cpo_strengths=indices[i],
239                    scale=25,
240                    scale_units="width",
241                )
242
243            fig_strength, ax_strength, colors = _vis.strengths(
244                None,
245                path_strains,
246                indices,
247                "CPO strength (M-index)",
248                markers,
249                labels,
250                colors=path_strains,
251                cmaps=cmaps,
252            )
253            fig_alignment, ax_alignment, colors = _vis.alignment(
254                None,
255                path_strains,
256                angles,
257                markers,
258                labels,
259                colors=path_strains,
260                cmaps=cmaps,
261            )
262
263            fig_domain.savefig(_io.resolve_path(f"{out_basepath}_path.pdf"))
264            fig_strength.savefig(_io.resolve_path(f"{out_basepath}_strength.pdf"))
265            fig_alignment.savefig(_io.resolve_path(f"{out_basepath}_angles.pdf"))

Tests for pure A-type olivine polycrystals in 2D corner flows.

class_id = 'corner_olivineA'
@classmethod
def run( cls, params, seed, get_velocity, get_velocity_gradient, min_coords, max_coords, max_strain, n_timesteps, final_location):
35    @classmethod
36    def run(
37        cls,
38        params,
39        seed,
40        get_velocity,
41        get_velocity_gradient,
42        min_coords,
43        max_coords,
44        max_strain,
45        n_timesteps,
46        final_location,
47    ):
48        """Run 2D corner flow A-type olivine simulation."""
49        mineral = _minerals.Mineral(
50            _core.MineralPhase.olivine,
51            _core.MineralFabric.olivine_A,
52            _core.DeformationRegime.matrix_dislocation,
53            n_grains=params["number_of_grains"],
54            seed=seed,
55        )
56        deformation_gradient = np.eye(3)
57        timestamps, get_position = _path.get_pathline(
58            final_location,
59            get_velocity,
60            get_velocity_gradient,
61            min_coords,
62            max_coords,
63            max_strain,
64            regular_steps=n_timesteps,
65        )
66        positions = [get_position(t) for t in timestamps]
67        velocity_gradients = [
68            get_velocity_gradient(np.nan, np.asarray(x)) for x in positions
69        ]
70        strains = np.empty_like(timestamps)
71        strains[0] = 0
72        for t, time in enumerate(timestamps[:-1], start=1):
73            strains[t] = strains[t - 1] + (
74                _utils.strain_increment(timestamps[t] - time, velocity_gradients[t])
75            )
76            _log.info(
77                "final location = %s; step %d/%d (ε = %.2f)",
78                final_location.ravel(),
79                t,
80                len(timestamps) - 1,
81                strains[t],
82            )
83
84            deformation_gradient = mineral.update_orientations(
85                params,
86                deformation_gradient,
87                get_velocity_gradient,
88                pathline=(time, timestamps[t], get_position),
89            )
90        return timestamps, positions, strains, mineral, deformation_gradient

Run 2D corner flow A-type olivine simulation.

@pytest.mark.slow
def test_steady4(self, outdir, seed, ncpus):
 92    @pytest.mark.slow
 93    def test_steady4(self, outdir, seed, ncpus):
 94        """Test CPO evolution in steady 2D corner flow along 4 pathlines.
 95
 96        Initial condition: random orientations and uniform volumes in all `Mineral`s.
 97
 98        Plate velocity: 2 cm/yr
 99
100        .. note::
101            This example takes about 11 CPU hours to run and uses around 60GB of RAM.
102            It is recommended to only use `ncpus=4` which matches the number of
103            pathlines, because higher numbers can lead to redundant cross-core
104            communication.
105
106        """
107        # Plate speed (half spreading rate), convert cm/yr to m/s.
108        plate_speed = 2.0 / (100.0 * 365.0 * 86400.0)
109        domain_height = 2.0e5  # Represents the depth of olivine-spinel transition.
110        domain_width = 1.0e6
111        params = _core.DefaultParams().as_dict()
112        params["number_of_grains"] = 5000
113        n_timesteps = 50  # Number of places along the pathline to compute CPO.
114        get_velocity, get_velocity_gradient = _velocity.corner_2d("X", "Z", plate_speed)
115        # Z-values at the end of each pathline.
116        z_ends = list(map(lambda x: x * domain_height, (-0.1, -0.3, -0.54, -0.78)))
117
118        # Optional plotting and logging setup.
119        optional_logging = cl.nullcontext()
120        if outdir is not None:
121            out_basepath = f"{outdir}/{SUBDIR}/{self.class_id}_prescribed"
122            optional_logging = _io.logfile_enable(f"{out_basepath}.log")
123            npzpath = pl.Path(f"{out_basepath}.npz")
124            labels = []
125            angles = []
126            indices = []
127            paths = []
128            path_strains = []
129            directions = []
130            max_grainsizes = []
131            timestamps = []
132
133        _run = ft.partial(
134            self.run,
135            params,
136            seed,
137            get_velocity,
138            get_velocity_gradient,
139            np.array([0.0, 0.0, -domain_height]),
140            np.array([domain_width, 0.0, 0.0]),
141            10,
142            n_timesteps,
143        )
144
145        final_locations = [np.array([domain_width, 0.0, z_exit]) for z_exit in z_ends]
146        with optional_logging:
147            _begin = perf_counter()
148            with Pool(processes=ncpus) as pool:
149                for i, out in enumerate(pool.imap(_run, final_locations)):
150                    times, positions, strains, mineral, deformation_gradient = out
151                    _log.info("final deformation gradient:\n%s", deformation_gradient)
152                    z_exit = z_ends[i]
153                    if outdir is not None:
154                        mineral.save(npzpath, _io.stringify(z_exit))
155
156                    misorient_angles = np.zeros(n_timesteps)
157                    bingham_vectors = np.zeros((n_timesteps, 3))
158                    orientations_resampled, f_resampled = _stats.resample_orientations(
159                        mineral.orientations, mineral.fractions, seed=seed
160                    )
161                    misorient_indices = _diagnostics.misorientation_indices(
162                        orientations_resampled,
163                        _geo.LatticeSystem.orthorhombic,
164                        pool=pool,
165                    )
166                    for idx, matrices in enumerate(orientations_resampled):
167                        direction_mean = _diagnostics.bingham_average(
168                            matrices,
169                            axis=_minerals.OLIVINE_PRIMARY_AXIS[mineral.fabric],
170                        )
171                        misorient_angles[idx] = _diagnostics.smallest_angle(
172                            direction_mean,
173                            np.asarray([1, 0, 0], dtype=np.float64),
174                        )
175                        bingham_vectors[idx] = direction_mean
176
177                    _log.debug("Total walltime: %s", perf_counter() - _begin)
178
179                    if outdir is not None:
180                        labels.append(rf"$z_{{f}}$ = {z_exit/1e3:.1f} km")
181                        angles.append(misorient_angles)
182                        indices.append(misorient_indices)
183                        paths.append(positions)
184                        max_grainsizes.append(
185                            np.asarray([np.max(f) for f in f_resampled])
186                        )
187                        path_strains.append(strains)
188                        timestamps.append(times)
189                        directions.append(bingham_vectors)
190
191        if outdir is not None:
192            np.savez(
193                f"{out_basepath}_path.npz",
194                strains_1=path_strains[0],
195                strains_2=path_strains[1],
196                strains_3=path_strains[2],
197                strains_4=path_strains[3],
198                paths_1=np.asarray(paths[0]),
199                paths_2=np.asarray(paths[1]),
200                paths_3=np.asarray(paths[2]),
201                paths_4=np.asarray(paths[3]),
202            )
203            np.savez(
204                f"{out_basepath}_strength.npz",
205                strength_1=indices[0],
206                strength_2=indices[1],
207                strength_3=indices[2],
208                strength_4=indices[3],
209            )
210            np.savez(
211                f"{out_basepath}_angles.npz",
212                angles_1=angles[0],
213                angles_2=angles[1],
214                angles_3=angles[2],
215                angles_4=angles[3],
216            )
217            markers = ("s", "o", "v", "*")
218            cmaps = ["cmc.batlow_r"] * len(markers)
219            fig_domain = _vis.figure(figsize=(10, 3))
220            ax_domain = fig_domain.add_subplot()
221            for i, z_exit in enumerate(z_ends):
222                if i == 0:
223                    resolution = [25, 5]
224                else:
225                    resolution = None
226                _vis.pathline_box2d(
227                    ax_domain,
228                    get_velocity,
229                    "xz",
230                    path_strains[i],
231                    paths[i],
232                    markers[i],
233                    [0, -domain_height],
234                    [domain_width + 1e5, 0],
235                    resolution,
236                    cmap=cmaps[i],
237                    cpo_vectors=directions[i],
238                    cpo_strengths=indices[i],
239                    scale=25,
240                    scale_units="width",
241                )
242
243            fig_strength, ax_strength, colors = _vis.strengths(
244                None,
245                path_strains,
246                indices,
247                "CPO strength (M-index)",
248                markers,
249                labels,
250                colors=path_strains,
251                cmaps=cmaps,
252            )
253            fig_alignment, ax_alignment, colors = _vis.alignment(
254                None,
255                path_strains,
256                angles,
257                markers,
258                labels,
259                colors=path_strains,
260                cmaps=cmaps,
261            )
262
263            fig_domain.savefig(_io.resolve_path(f"{out_basepath}_path.pdf"))
264            fig_strength.savefig(_io.resolve_path(f"{out_basepath}_strength.pdf"))
265            fig_alignment.savefig(_io.resolve_path(f"{out_basepath}_angles.pdf"))

Test CPO evolution in steady 2D corner flow along 4 pathlines.

Initial condition: random orientations and uniform volumes in all Minerals.

Plate velocity: 2 cm/yr

This example takes about 11 CPU hours to run and uses around 60GB of RAM. It is recommended to only use ncpus=4 which matches the number of pathlines, because higher numbers can lead to redundant cross-core communication.