1# -*- coding: utf-8 -*-
2"""
3Random Vibration utilities
4
5Rattlesnake Vibration Control Software
6Copyright (C) 2021 National Technology & Engineering Solutions of Sandia, LLC
7(NTESS). Under the terms of Contract DE-NA0003525 with NTESS, the U.S.
8Government retains certain rights in this software.
9
10This program is free software: you can redistribute it and/or modify
11it under the terms of the GNU General Public License as published by
12the Free Software Foundation, either version 3 of the License, or
13(at your option) any later version.
14
15This program is distributed in the hope that it will be useful,
16but WITHOUT ANY WARRANTY; without even the implied warranty of
17MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
18GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program. If not, see <https://www.gnu.org/licenses/>.
22"""
23import os
24import numpy as np
25from scipy.io import loadmat
26from .utilities import reduce_array_by_coordinate
27
28_direction_map = {
29 "X+": 1,
30 "X": 1,
31 "+X": 1,
32 "Y+": 2,
33 "Y": 2,
34 "+Y": 2,
35 "Z+": 3,
36 "Z": 3,
37 "+Z": 3,
38 "RX+": 4,
39 "RX": 4,
40 "+RX": 4,
41 "RY+": 5,
42 "RY": 5,
43 "+RY": 5,
44 "RZ+": 6,
45 "RZ": 6,
46 "+RZ": 6,
47 "X-": -1,
48 "-X": -1,
49 "Y-": -2,
50 "-Y": -2,
51 "Z-": -3,
52 "-Z": -3,
53 "RX-": -4,
54 "-RX": -4,
55 "RY-": -5,
56 "-RY": -5,
57 "RZ-": -6,
58 "-RZ": -6,
59 "": 0,
60}
61
62
63def load_specification(spec_path, n_freq_lines, df, control_dofs=None):
64 """Loads a specification CPSD matrix from a file.
65
66 Parameters
67 ----------
68 spec_path : str
69 Loads the specification contained in this file
70 n_freq_lines : int
71 The number of frequency lines
72 df : float
73 The frequency spacing
74 control_dofs : list | np.ndarray | None
75 1-D array of DOFs to use for control. If coordinates are provided in spec file,
76 the cpsd matrix will be indexed to match the provided control DOFs.
77
78 Returns
79 -------
80 frequency_lines : np.ndarray
81 The frequency lines ``df*np.arange(n_freq_lines)``
82 cpsd_matrix : np.ndarray
83 3D numpy array consisting of a CPSD matrix at each frequency line
84 """
85 _, extension = os.path.splitext(spec_path)
86 if extension.lower() == ".mat":
87 data = loadmat(spec_path)
88 frequencies = data["f"].squeeze()
89 cpsd = data["cpsd"].transpose(2, 0, 1)
90 warning_upper = data["warning_upper"].transpose(1, 0) if "warning_upper" in data else None
91 warning_lower = data["warning_lower"].transpose(1, 0) if "warning_lower" in data else None
92 abort_upper = data["abort_upper"].transpose(1, 0) if "abort_upper" in data else None
93 abort_lower = data["abort_lower"].transpose(1, 0) if "abort_lower" in data else None
94 elif extension.lower() == ".npz":
95 data = np.load(spec_path)
96 frequencies = data["f"].squeeze()
97 cpsd = data["cpsd"]
98 warning_upper = data["warning_upper"] if "warning_upper" in data else None
99 warning_lower = data["warning_lower"] if "warning_lower" in data else None
100 abort_upper = data["abort_upper"] if "abort_upper" in data else None
101 abort_lower = data["abort_lower"] if "abort_lower" in data else None
102 coordinate = data["coordinate"] if "coordinate" in data else None
103 if coordinate is not None and control_dofs is not None:
104 cpsd = reduce_array_by_coordinate(cpsd, coordinate, control_dofs)
105 limit_coordinate = coordinate.diagonal().T
106 warning_upper = (
107 reduce_array_by_coordinate(warning_upper, limit_coordinate, control_dofs)
108 if warning_upper is not None
109 else None
110 )
111 warning_lower = (
112 reduce_array_by_coordinate(warning_lower, limit_coordinate, control_dofs)
113 if warning_lower is not None
114 else None
115 )
116 abort_upper = (
117 reduce_array_by_coordinate(abort_upper, limit_coordinate, control_dofs)
118 if abort_upper is not None
119 else None
120 )
121 abort_lower = (
122 reduce_array_by_coordinate(abort_lower, limit_coordinate, control_dofs)
123 if abort_lower is not None
124 else None
125 )
126 else:
127 raise ValueError("Invalid File Format")
128 # Create the full CPSD matrix
129 frequency_lines = df * np.arange(n_freq_lines)
130 cpsd_matrix = np.zeros((n_freq_lines,) + cpsd.shape[1:], dtype="complex128")
131 warning_matrix = np.empty((2, n_freq_lines, cpsd.shape[-1]), dtype="float64")
132 warning_matrix[:] = np.nan
133 abort_matrix = np.empty((2, n_freq_lines, cpsd.shape[-1]), dtype="float64")
134 abort_matrix[:] = np.nan
135 for i, (frequency, cpsd_line) in enumerate(zip(frequencies, cpsd)):
136 index = np.argmin(np.abs(frequency - frequency_lines))
137 if abs(frequency - frequency_lines[index]) > 1e-5:
138 continue
139 cpsd_matrix[index, ...] = cpsd_line
140 if warning_lower is not None:
141 warning_matrix[0, index] = warning_lower[i]
142 if warning_upper is not None:
143 warning_matrix[1, index] = warning_upper[i]
144 if abort_lower is not None:
145 abort_matrix[0, index] = abort_lower[i]
146 if abort_upper is not None:
147 abort_matrix[1, index] = abort_upper[i]
148 # Deliever specification to data analysis
149 return frequency_lines, cpsd_matrix, warning_matrix, abort_matrix