.. _rotate-origin-potential: For code blocks below, we assume the following imports have already been executed:: >>> import astropy.units as u >>> import numpy as np >>> import gala.potential as gp >>> from gala.units import galactic, solarsystem ************************************************************** Specifying rotations or origin shifts to ``Potential`` classes ************************************************************** Most of the gravitational potential classes implemented in ``gala`` support shifting the origin of the potential relative to the coordinate system, and specifying a rotation of the potential relative to the coordinate system. By default, the origin is assumed to be at (0,0,0) or (0,0), and there is no rotation assumed. Origin shifts ============= For potential classes that support these options, origin shifts are specified by passing in a `~astropy.units.Quantity` to set the origin of the potential in the given coordinate system. For example, if we are working with two `~gala.potential.KeplerPotential` objects, and we want them to be offset from one another such that one potential is at ``(1, 0, 0)`` AU and the other is at ``(-2, 0, 0)`` AU, we would define the two objects as:: >>> p1 = gp.KeplerPotential(m=1*u.Msun, origin=[1, 0, 0]*u.au, ... units=solarsystem) >>> p2 = gp.KeplerPotential(m=0.5*u.Msun, origin=[-2, 0, 0]*u.au, ... units=solarsystem) To see that these are shifted from the coordinate system origin, let's combine these two objects into a `~gala.potential.CCompositePotential` and visualize the potential:: >>> pot = gp.CCompositePotential(p1=p1, p2=p2) >>> fig, ax = plt.subplots(1, 1, figsize=(5, 5)) # doctest: +SKIP >>> grid = np.linspace(-5, 5, 100) >>> p.plot_contours(grid=(grid, grid, 0.), ax=ax) # doctest: +SKIP >>> ax.set_xlabel("$x$ [kpc]") # doctest: +SKIP >>> ax.set_ylabel("$y$ [kpc]") # doctest: +SKIP .. plot:: :align: center :context: close-figs import astropy.units as u import matplotlib.pyplot as plt import numpy as np import gala.potential as gp from gala.units import galactic, solarsystem p1 = gp.KeplerPotential(m=1*u.Msun, origin=[1, 0, 0]*u.au, units=solarsystem) p2 = gp.KeplerPotential(m=0.5*u.Msun, origin=[-2, 0, 0]*u.au, units=solarsystem) pot = gp.CCompositePotential(p1=p1, p2=p2) fig, ax = plt.subplots(1, 1, figsize=(5, 5)) grid = np.linspace(-5, 5, 100) pot.plot_contours(grid=(grid, grid, 0.), ax=ax) # doctest: +SKIP ax.set_xlabel("$x$ [kpc]") # doctest: +SKIP ax.set_ylabel("$y$ [kpc]") # doctest: +SKIP fig.tight_layout() Rotations ========= Rotations can be specified either by passing in a `scipy.spatial.transform.Rotation` instance, or by passing in a 2D numpy array specifying a rotation matrix. For example, let's see what happens if we rotate a bar potential using these two possible inputs. First, we'll define a rotation matrix specifying a 30 degree rotation around the z axis (i.e. counter-clockwise) using `astropy.coordinates.matrix_utilities.rotation_matrix`. Next, we'll define a rotation using a scipy `~scipy.spatial.transform.Rotation` object:: >>> from astropy.coordinates.matrix_utilities import rotation_matrix >>> from scipy.spatial.transform import Rotation >>> R_arr = rotation_matrix(30*u.deg, 'z') >>> R_scipy = Rotation.from_euler('z', 30, degrees=True) .. warning:: Note that astropy and scipy have different rotation conventions, so even though both of the above look like identical 30 degree rotations around the z axis, they result in different (i.e. transposed or inverse) rotation matrices:: >>> R_arr # doctest: +FLOAT_CMP array([[ 0.8660254, 0.5 , 0. ], [-0.5 , 0.8660254, 0. ], [ 0. , 0. , 1. ]]) >>> R_scipy.as_matrix() array([[ 0.8660254, -0.5 , 0. ], [ 0.5 , 0.8660254, 0. ], [ 0. , 0. , 1. ]]) Let's see what happens to the bar potential when we specify these rotations:: >>> bar1 = gp.LongMuraliBarPotential(m=1e10, a=3.5, b=0.5, c=0.5, ... units=galactic) >>> bar2 = gp.LongMuraliBarPotential(m=1e10, a=3.5, b=0.5, c=0.5, ... units=galactic, R=R_arr) >>> bar3 = gp.LongMuraliBarPotential(m=1e10, a=3.5, b=0.5, c=0.5, ... units=galactic, R=R_scipy) .. plot:: :align: center :context: close-figs from astropy.coordinates.matrix_utilities import rotation_matrix from scipy.spatial.transform import Rotation R_arr = rotation_matrix(30*u.deg, 'z') R_scipy = Rotation.from_euler('z', 30, degrees=True) fig, axes = plt.subplots(1, 3, figsize=(15, 5), sharex=True, sharey=True) grid = np.linspace(-5, 5, 100) bar1 = gp.LongMuraliBarPotential(m=1e10, a=3.5, b=0.5, c=0.5, units=galactic) bar2 = gp.LongMuraliBarPotential(m=1e10, a=3.5, b=0.5, c=0.5, units=galactic, R=R_arr) bar3 = gp.LongMuraliBarPotential(m=1e10, a=3.5, b=0.5, c=0.5, units=galactic, R=R_scipy) bar1.plot_contours(grid=(grid, grid, 0.), ax=axes[0]) bar2.plot_contours(grid=(grid, grid, 0.), ax=axes[1]) bar3.plot_contours(grid=(grid, grid, 0.), ax=axes[2]) axes[0].set_xlabel("$x$ [kpc]") # doctest: +SKIP axes[0].set_ylabel("$y$ [kpc]") # doctest: +SKIP axes[1].set_xlabel("$x$ [kpc]") # doctest: +SKIP axes[2].set_xlabel("$x$ [kpc]") # doctest: +SKIP fig.tight_layout()