def optimize_rotation(self, ellipsoid, e_matrix, init_rot): """Optimize a rotation.""" self._check_ellipsoid(ellipsoid) axes, angles = unwrap_euler_angles(init_rot) orig_strain = to_full_tensor(self.eigenstrain.copy()) aspect = np.array(ellipsoid["aspect"]) self.eshelby = StrainEnergy.get_eshelby(aspect, self.poisson) opt_args = { "ellipsoid": ellipsoid, "elast_matrix": e_matrix, "strain_energy_obj": self, "orig_strain": orig_strain, "rot_axes": axes } opts = {"eps": 1.0} res = minimize(cost_minimize_strain_energy, angles, args=(opt_args, ), options=opts) rot_seq = combine_axes_and_angles(axes, res["x"]) optimal_orientation = { "energy": res["fun"], "rot_sequence": rot_seq, "rot_matrix": rot_matrix(rot_seq) } # Reset the eigenstrain back self.eigenstrain = to_voigt(orig_strain) return optimal_orientation
def plot(self, scale_factor, elast_matrix, rot_seq=None, latex=False): """Create a plot of the energy as different aspect ratios.""" from matplotlib import pyplot as plt a_over_c = np.logspace(0, 3, 100) b_over_c = [1, 2, 5, 10, 50, 100] orig_strain = self.eigenstrain.copy() if rot_seq is not None: strain = to_full_tensor(orig_strain) rot_mat = rot_matrix(rot_seq) strain = rotate_tensor(strain, rot_mat) self.eigenstrain = to_voigt(strain) fig = plt.figure() ax = fig.add_subplot(1, 1, 1) for b in b_over_c: W = [] a_plot = [] for a in a_over_c: if a < b: continue aspect = [a, b, 1.0] self.eshelby = StrainEnergy.get_eshelby(aspect, self.poisson) E = self.strain_energy(scale_factor, elast_matrix) * 1000.0 W.append(E) a_plot.append(a) ax.plot(a_plot, W, label="{}".format(int(b))) # Separation line b_sep = np.logspace(0, 3, 100) W = [] for b in b_sep: aspect = [b, b, 1.0] self.eshelby = StrainEnergy.get_eshelby(aspect, self.poisson) E = self.strain_energy(scale_factor, elast_matrix) * 1000.0 W.append(E) ax.plot(b_sep, W, "--", color="grey") ax.legend(frameon=False, loc="best") if latex: xlab = "\$a/c\$" ylab = "Strain energy (meV/\$\SI{}{\\angstrom^3}\$)" else: xlab = "$a/c$" ylab = "Strain enregy (meV/A^3)" ax.set_xlabel(xlab) ax.set_ylabel(ylab) ax.spines["right"].set_visible(False) ax.spines["top"].set_visible(False) ax.set_xscale("log") self.eigenstrain = orig_strain return fig
def explore_orientations(self, voxels, theta_ax="y", phi_ax="z", step=5, theta_min=0, theta_max=180, phi_min=0, phi_max=360, fname=None): """Explore orientation dependency of the strain energy. :param np.ndarray voxels: Voxel representation of the geometry :param str theta_ax: Rotation axis for theta angle :param str phi_ax: Rotation axis for phi angle :param int step: Angle change in degress :param int theta_min: Start angle for theta :param int theta_max: End angle for theta :param int phi_min: Start angle for phi :param int phi_max: End angle for phi :param fname str: Filename for storing the output result """ th = list(range(theta_min, theta_max, step)) ph = list(range(phi_min, phi_max, step)) misfit_orig = self.misfit_strain.copy() orig_C = self.C.copy() result = [] now = time.time() status_interval = 30 for ang in product(th, ph): if time.time() - now > status_interval: print("Theta: {}, Phi: {}".format(ang[0], ang[1])) now = time.time() seq = [(theta_ax, -ang[0]), (phi_ax, -ang[1])] matrix = rot_matrix(seq) # Rotate the strain tensor self.misfit_strain = rotate_tensor(misfit_orig, matrix) self.C = rotate_rank4_tensor(orig_C.copy(), matrix) energy = self.strain_energy_voxels(voxels) result.append([ang[0], ang[1], energy]) if fname is None: fname = "khacaturyan_orientaions{}.csv".format(timestamp) np.savetxt(fname, np.array(result), delimiter=",", header="Theta ({}) deg, Phi ({}) deg, Energy (eV)".format( theta_ax, phi_ax)) print("Results of orientation exploration written to {}".format(fname))
def cost_minimize_strain_energy(euler, args): """Cost function used to minimize.""" ellipsoid = args["ellipsoid"] scale_factor = ellipsoid["scale_factor"] e_matrix = args["elast_matrix"] orig_strain = args["orig_strain"] obj = args["strain_energy_obj"] rot_axes = args["rot_axes"] rot_seq = combine_axes_and_angles(rot_axes, euler) matrix = rot_matrix(rot_seq) tensor = rotate_tensor(orig_strain, matrix) tensor = to_voigt(tensor) obj.eigenstrain = tensor return obj.strain_energy(scale_factor, e_matrix)
def show_ellipsoid(self, ellipsoid, rot_seq): """Show the ellipsoid at given orientation.""" from matplotlib import pyplot as plt matrix = rot_matrix(rot_seq) coefs = np.array(ellipsoid["aspect"]) # Spherical angles u = np.linspace(0, 2.0 * np.pi, 100) v = np.linspace(0, np.pi, 100) rx, ry, rz = coefs x = rx * np.outer(np.cos(u), np.sin(v)) y = ry * np.outer(np.sin(u), np.sin(v)) z = rz * np.outer(np.ones_like(u), np.cos(v)) N_pos = x.shape[0] * x.shape[1] # Pack x, y, z into a numpy matrix all_coords = np.zeros((3, N_pos)) all_coords[0, :] = np.ravel(x) all_coords[1, :] = np.ravel(y) all_coords[2, :] = np.ravel(z) # Rotate all. Use the transpose of the rotation matrix because # the rotation matrix is intended to be used for rotating the # coordinate system, keeping the ellipsoid fixed # so the inverse rotation is required when rotating the ellipsoid all_coords = matrix.T.dot(all_coords) x = np.reshape(all_coords[0, :], x.shape) y = np.reshape(all_coords[1, :], y.shape) z = np.reshape(all_coords[2, :], z.shape) # Adjustment of the axes, so that they all have the same span: fig = plt.figure() ax = fig.add_subplot(111, projection='3d') ax.plot_surface(x, y, z, rstride=4, cstride=4, color="grey") max_radius = max(rx, ry, rz) for axis in 'xyz': getattr(ax, 'set_{}lim'.format(axis))((-max_radius, max_radius)) return fig
def explore_orientations(self, ellipsoid, C_matrix, step=10, fname="", theta_ax="y", phi_ax="z"): """Explore the strain energy as a function of ellipse orientation. :param dict ellipsoid: Dictionary with information of the ellipsoid The format should be {"aspect": [1.0, 1.0, 1.0], "C_prec": ..., "scale_factor": 1.0} C_prec is the elastic tensor of the precipitate material. If not given, scale_factor has to be given, and the elastic tensor of the precipitate material is taken as this factor multiplied by the elastic tensor of the matrix material :param numpy.ndarray C_matrix: Elastic tensor of the matrix material :param float step: Angle step size in degree :param str fname: If given the result of the exploration is stored in a csv file with this filename :param str theta_ax: The first rotation is performed around this axis :param str phi_ax: The second rotation is performed around this axis (in the new coordinate system after the first rotation is performed) """ from itertools import product #self._check_ellipsoid(ellipsoid) scale_factor = ellipsoid.get("scale_factor", None) C_prec = ellipsoid.get("C_prec", None) if C_prec is None: C_prec = scale_factor * C_matrix # Convert the tensors to their isotropic representation C_matrix = self.make_isotropic(C_matrix) C_prec = self.make_isotropic(C_prec) aspect = np.array(ellipsoid["aspect"]) self.eshelby = StrainEnergy.get_eshelby(aspect, self.poisson) result = [] misfit_orig = to_full_tensor(self.misfit) theta = np.arange(0.0, np.pi, step * np.pi / 180.0) phi = np.arange(0.0, 2.0 * np.pi, step * np.pi / 180.0) theta = np.append(theta, [np.pi]) phi = np.append(phi, [2.0 * np.pi]) C_matrix_orig = C_matrix.copy() C_prec_orig = C_prec.copy() for ang in product(theta, phi): th = ang[0] p = ang[1] theta_deg = th * 180 / np.pi phi_deg = p * 180 / np.pi seq = [(theta_ax, -theta_deg), (phi_ax, -phi_deg)] matrix = rot_matrix(seq) #matrix = rot_matrix_spherical_coordinates(p, th) # Rotate the strain tensor strain = rotate_tensor(misfit_orig, matrix) self.misfit = to_mandel(strain) # Rotate the elastic tensor of the matrix material C_matrix = rotate_rank4_mandel(C_matrix_orig, matrix) # Rotate the elastic tensor of the precipitate material C_prec = rotate_rank4_mandel(C_prec_orig, matrix) if abs(p) < 1E-3 and (abs(th - np.pi / 4.0) < 1E-3 or abs(th - 3.0 * np.pi / 4.0) < 1E-3): print(self.eshelby.aslist()) energy = self.strain_energy(C_matrix=C_matrix, C_prec=C_prec) res = {"energy": energy, "theta": th, "phi": p} a = matrix.T.dot([1.0, 0.0, 0.0]) b = matrix.T.dot([0.0, 1.0, 0.0]) c = matrix.T.dot([0.0, 0.0, 1.0]) res["half_axes"] = {"a": a, "b": b, "c": c} res["misfit"] = self.misfit result.append(res) if fname != "": self.save_orientation_result(result, fname) # Sort the result from low energy to high energy energies = [res["energy"] for res in result] sorted_indx = np.argsort(energies) result = [result[indx] for indx in sorted_indx] # Reset the strain self.misfit = to_mandel(misfit_orig) return result