scirs2_autograd/tensor_ops/
matrix_trig_functions.rs1use crate::op::{ComputeContext, GradientContext, Op, OpError};
2use crate::tensor::Tensor;
3use crate::tensor_ops::matrix_calculus::{
4 self, MatrixFnKind, MatrixFnVjpOp, ScalarFnSymmetricVjpOp,
5};
6use crate::Float;
7use scirs2_core::ndarray::{Array1, Array2, Ix2};
8use scirs2_core::numeric::FromPrimitive;
9
10fn append_matrix_fn_grad<F: Float>(ctx: &mut GradientContext<F>, kind: MatrixFnKind) {
20 let a = *ctx.input(0);
21 let gy = *ctx.output_grad();
22 let g = ctx.graph();
23 let gx = Tensor::builder(g)
24 .append_input(a, false)
25 .append_input(gy, false)
26 .build(MatrixFnVjpOp { kind });
27 ctx.append_input_grad(0, Some(gx));
28}
29
30pub struct MatrixSineOp;
32
33impl<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive> Op<F> for MatrixSineOp {
34 fn name(&self) -> &'static str {
35 "MatrixSine"
36 }
37
38 fn compute(&self, ctx: &mut ComputeContext<F>) -> Result<(), OpError> {
39 let input = ctx.input(0);
40 let shape = input.shape();
41
42 if shape.len() != 2 || shape[0] != shape[1] {
43 return Err(OpError::IncompatibleShape(
44 "Matrix sine requires square matrix".into(),
45 ));
46 }
47
48 let input_2d = input
49 .view()
50 .into_dimensionality::<Ix2>()
51 .map_err(|_| OpError::IncompatibleShape("Failed to convert to 2D".into()))?;
52
53 let result = compute_matrix_sine(&input_2d)?;
54 ctx.append_output(result.into_dyn());
55 Ok(())
56 }
57
58 fn grad(&self, ctx: &mut GradientContext<F>) {
59 append_matrix_fn_grad(ctx, MatrixFnKind::Sin);
60 }
61}
62
63pub struct MatrixCosineOp;
65
66impl<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive> Op<F> for MatrixCosineOp {
67 fn name(&self) -> &'static str {
68 "MatrixCosine"
69 }
70
71 fn compute(&self, ctx: &mut ComputeContext<F>) -> Result<(), OpError> {
72 let input = ctx.input(0);
73 let shape = input.shape();
74
75 if shape.len() != 2 || shape[0] != shape[1] {
76 return Err(OpError::IncompatibleShape(
77 "Matrix cosine requires square matrix".into(),
78 ));
79 }
80
81 let input_2d = input
82 .view()
83 .into_dimensionality::<Ix2>()
84 .map_err(|_| OpError::IncompatibleShape("Failed to convert to 2D".into()))?;
85
86 let result = compute_matrix_cosine(&input_2d)?;
87 ctx.append_output(result.into_dyn());
88 Ok(())
89 }
90
91 fn grad(&self, ctx: &mut GradientContext<F>) {
92 append_matrix_fn_grad(ctx, MatrixFnKind::Cos);
93 }
94}
95
96pub struct MatrixSignOp;
98
99impl<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive> Op<F> for MatrixSignOp {
100 fn name(&self) -> &'static str {
101 "MatrixSign"
102 }
103
104 fn compute(&self, ctx: &mut ComputeContext<F>) -> Result<(), OpError> {
105 let input = ctx.input(0);
106 let shape = input.shape();
107
108 if shape.len() != 2 || shape[0] != shape[1] {
109 return Err(OpError::IncompatibleShape(
110 "Matrix sign requires square matrix".into(),
111 ));
112 }
113
114 let input_2d = input
115 .view()
116 .into_dimensionality::<Ix2>()
117 .map_err(|_| OpError::IncompatibleShape("Failed to convert to 2D".into()))?;
118
119 let result = compute_matrix_sign(&input_2d)?;
120 ctx.append_output(result.into_dyn());
121 Ok(())
122 }
123
124 fn grad(&self, ctx: &mut GradientContext<F>) {
125 append_matrix_fn_grad(ctx, MatrixFnKind::Sign);
131 }
132}
133
134pub struct MatrixSinhOp;
136
137impl<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive> Op<F> for MatrixSinhOp {
138 fn name(&self) -> &'static str {
139 "MatrixSinh"
140 }
141
142 fn compute(&self, ctx: &mut ComputeContext<F>) -> Result<(), OpError> {
143 let input = ctx.input(0);
144 let shape = input.shape();
145
146 if shape.len() != 2 || shape[0] != shape[1] {
147 return Err(OpError::IncompatibleShape(
148 "Matrix sinh requires square matrix".into(),
149 ));
150 }
151
152 let input_2d = input
153 .view()
154 .into_dimensionality::<Ix2>()
155 .map_err(|_| OpError::IncompatibleShape("Failed to convert to 2D".into()))?;
156
157 let result = compute_matrix_sinh(&input_2d)?;
158 ctx.append_output(result.into_dyn());
159 Ok(())
160 }
161
162 fn grad(&self, ctx: &mut GradientContext<F>) {
163 append_matrix_fn_grad(ctx, MatrixFnKind::Sinh);
164 }
165}
166
167pub struct MatrixCoshOp;
169
170impl<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive> Op<F> for MatrixCoshOp {
171 fn name(&self) -> &'static str {
172 "MatrixCosh"
173 }
174
175 fn compute(&self, ctx: &mut ComputeContext<F>) -> Result<(), OpError> {
176 let input = ctx.input(0);
177 let shape = input.shape();
178
179 if shape.len() != 2 || shape[0] != shape[1] {
180 return Err(OpError::IncompatibleShape(
181 "Matrix cosh requires square matrix".into(),
182 ));
183 }
184
185 let input_2d = input
186 .view()
187 .into_dimensionality::<Ix2>()
188 .map_err(|_| OpError::IncompatibleShape("Failed to convert to 2D".into()))?;
189
190 let result = compute_matrix_cosh(&input_2d)?;
191 ctx.append_output(result.into_dyn());
192 Ok(())
193 }
194
195 fn grad(&self, ctx: &mut GradientContext<F>) {
196 append_matrix_fn_grad(ctx, MatrixFnKind::Cosh);
197 }
198}
199
200pub struct MatrixFunctionOp<F: Float> {
202 function: fn(F) -> F,
203 name: &'static str,
204}
205
206impl<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive> Op<F> for MatrixFunctionOp<F> {
207 fn name(&self) -> &'static str {
208 self.name
209 }
210
211 fn compute(&self, ctx: &mut ComputeContext<F>) -> Result<(), OpError> {
212 let input = ctx.input(0);
213 let shape = input.shape();
214
215 if shape.len() != 2 || shape[0] != shape[1] {
216 return Err(OpError::IncompatibleShape(
217 "Matrix function requires square matrix".into(),
218 ));
219 }
220
221 let input_2d = input
222 .view()
223 .into_dimensionality::<Ix2>()
224 .map_err(|_| OpError::IncompatibleShape("Failed to convert to 2D".into()))?;
225
226 let result = compute_matrix_function(&input_2d, self.function)?;
227 ctx.append_output(result.into_dyn());
228 Ok(())
229 }
230
231 fn grad(&self, ctx: &mut GradientContext<F>) {
232 let a = *ctx.input(0);
236 let gy = *ctx.output_grad();
237 let g = ctx.graph();
238 let gx = Tensor::builder(g)
239 .append_input(a, false)
240 .append_input(gy, false)
241 .build(ScalarFnSymmetricVjpOp {
242 function: self.function,
243 });
244 ctx.append_input_grad(0, Some(gx));
245 }
246}
247
248#[allow(dead_code)]
256fn compute_matrix_sine<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
257 matrix: &scirs2_core::ndarray::ArrayView2<F>,
258) -> Result<Array2<F>, OpError> {
259 matrix_calculus::sin_cos_m(matrix).map(|(sin, _)| sin)
260}
261
262#[allow(dead_code)]
264fn compute_matrix_cosine<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
265 matrix: &scirs2_core::ndarray::ArrayView2<F>,
266) -> Result<Array2<F>, OpError> {
267 matrix_calculus::sin_cos_m(matrix).map(|(_, cos)| cos)
268}
269
270#[allow(dead_code)]
272fn compute_matrix_sign<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
273 matrix: &scirs2_core::ndarray::ArrayView2<F>,
274) -> Result<Array2<F>, OpError> {
275 matrix_calculus::signm(matrix)
276}
277
278#[allow(dead_code)]
280fn compute_matrix_sinh<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
281 matrix: &scirs2_core::ndarray::ArrayView2<F>,
282) -> Result<Array2<F>, OpError> {
283 matrix_calculus::sinh_cosh_m(matrix).map(|(sinh, _)| sinh)
284}
285
286#[allow(dead_code)]
288fn compute_matrix_cosh<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
289 matrix: &scirs2_core::ndarray::ArrayView2<F>,
290) -> Result<Array2<F>, OpError> {
291 matrix_calculus::sinh_cosh_m(matrix).map(|(_, cosh)| cosh)
292}
293
294#[allow(dead_code)]
304fn compute_matrix_function<F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
305 matrix: &scirs2_core::ndarray::ArrayView2<F>,
306 func: fn(F) -> F,
307) -> Result<Array2<F>, OpError> {
308 let n = matrix.shape()[0];
309
310 if !matrix_calculus::is_symmetric(matrix) {
311 return Err(OpError::Other(
312 "funm: applying a scalar function through the spectrum is only supported for \
313 symmetric matrices (a non-symmetric argument needs a Schur-Parlett \
314 evaluation, which this crate does not provide)"
315 .into(),
316 ));
317 }
318
319 let (eigenvalues, eigenvectors) = matrix_calculus::symmetric_eigen(matrix)?;
320
321 let mut temp = Array2::<F>::zeros((n, n));
322 for i in 0..n {
323 for j in 0..n {
324 temp[[i, j]] = eigenvectors[[i, j]] * func(eigenvalues[j]);
325 }
326 }
327 Ok(temp.dot(&eigenvectors.t()))
328}
329
330#[allow(dead_code)]
334pub fn sinm<'g, F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
335 matrix: &Tensor<'g, F>,
336) -> Tensor<'g, F> {
337 let g = matrix.graph();
338 let matrixshape = crate::tensor_ops::shape(matrix);
339
340 Tensor::builder(g)
341 .append_input(matrix, false)
342 .setshape(&matrixshape)
343 .build(MatrixSineOp)
344}
345
346#[allow(dead_code)]
348pub fn cosm<'g, F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
349 matrix: &Tensor<'g, F>,
350) -> Tensor<'g, F> {
351 let g = matrix.graph();
352 let matrixshape = crate::tensor_ops::shape(matrix);
353
354 Tensor::builder(g)
355 .append_input(matrix, false)
356 .setshape(&matrixshape)
357 .build(MatrixCosineOp)
358}
359
360#[allow(dead_code)]
362pub fn signm<'g, F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
363 matrix: &Tensor<'g, F>,
364) -> Tensor<'g, F> {
365 let g = matrix.graph();
366 let matrixshape = crate::tensor_ops::shape(matrix);
367
368 Tensor::builder(g)
369 .append_input(matrix, false)
370 .setshape(&matrixshape)
371 .build(MatrixSignOp)
372}
373
374#[allow(dead_code)]
376pub fn sinhm<'g, F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
377 matrix: &Tensor<'g, F>,
378) -> Tensor<'g, F> {
379 let g = matrix.graph();
380 let matrixshape = crate::tensor_ops::shape(matrix);
381
382 Tensor::builder(g)
383 .append_input(matrix, false)
384 .setshape(&matrixshape)
385 .build(MatrixSinhOp)
386}
387
388#[allow(dead_code)]
390pub fn coshm<'g, F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
391 matrix: &Tensor<'g, F>,
392) -> Tensor<'g, F> {
393 let g = matrix.graph();
394 let matrixshape = crate::tensor_ops::shape(matrix);
395
396 Tensor::builder(g)
397 .append_input(matrix, false)
398 .setshape(&matrixshape)
399 .build(MatrixCoshOp)
400}
401
402#[allow(dead_code)]
404pub fn funm<'g, F: Float + scirs2_core::ndarray::ScalarOperand + FromPrimitive>(
405 matrix: &Tensor<'g, F>,
406 func: fn(F) -> F,
407 name: &'static str,
408) -> Tensor<'g, F> {
409 let g = matrix.graph();
410 let matrixshape = crate::tensor_ops::shape(matrix);
411
412 Tensor::builder(g)
413 .append_input(matrix, false)
414 .setshape(&matrixshape)
415 .build(MatrixFunctionOp {
416 function: func,
417 name,
418 })
419}