qudit_inst/numerical/functions/
hs.rs1use std::sync::Arc;
2
3use super::super::InstantiationProblem;
4use crate::InstantiationTarget;
5use crate::numerical::Hessian;
6use crate::numerical::Jacobian;
7use crate::numerical::ProvidesJacobian;
8use crate::numerical::ProvidesResidualFunction;
9use crate::numerical::ResidualFunction;
10use crate::numerical::function::CostFunction;
11use crate::numerical::function::Function;
12use crate::numerical::function::Gradient;
13use crate::numerical::problem::Problem;
14use crate::numerical::problem::ProvidesCostFunction;
15use faer::ColMut;
16use faer::MatMut;
17use faer::RowMut;
18use faer::reborrow::ReborrowMut;
19use qudit_circuit::QuditCircuit;
20use qudit_core::ComplexScalar;
21use qudit_core::RealScalar;
22use qudit_expr::DifferentiationLevel;
23use qudit_expr::FUNCTION;
24use qudit_expr::GRADIENT;
25use qudit_tensor::PinnedTNVM;
27use qudit_tensor::TNVM;
28use qudit_tensor::compile_network;
29
30#[derive(Clone)]
31pub struct HSProblem<R: RealScalar> {
32 pub circuit: Arc<QuditCircuit>,
33 pub target: Arc<InstantiationTarget<R::C>>,
34}
35
36impl<R: RealScalar> HSProblem<R> {
37 pub fn new(circuit: Arc<QuditCircuit>, target: Arc<InstantiationTarget<R::C>>) -> Self {
38 HSProblem { circuit, target }
39 }
40
41 pub fn build_cost<const D: DifferentiationLevel>(&self) -> PinnedTNVM<R::C, D> {
42 let mut builder = self.circuit.as_tensor_network_builder();
43 match &*self.target {
47 InstantiationTarget::UnitaryMatrix(u) => {
48 let qudits = builder.open_output_indices();
49 builder = builder.prepend_unitary(u.dagger(), qudits);
50 }
51 }
52
53 let code = compile_network(builder.trace_all_open_wires().build());
54 TNVM::<R::C, D>::new(&code, Some(&self.circuit.params().const_map()))
55 }
56
57 pub fn build_residual<const D: DifferentiationLevel>(&self) -> PinnedTNVM<R::C, D> {
58 let mut builder = self.circuit.as_tensor_network_builder();
60 match &*self.target {
64 InstantiationTarget::UnitaryMatrix(u) => {
65 let qudits = builder.open_output_indices();
66 builder = builder.prepend_unitary(u.dagger(), qudits);
67 }
68 }
69
70 let code = compile_network(builder.build());
71 TNVM::<R::C, D>::new(&code, Some(&self.circuit.params().const_map()))
73 }
74}
75
76impl<R: RealScalar> InstantiationProblem<R> for HSProblem<R> {
77 fn from_instantiation(
78 circuit: Arc<QuditCircuit>,
79 target: Arc<InstantiationTarget<R::C>>,
80 _data: Arc<crate::DataMap>,
81 ) -> Self {
82 Self::new(circuit, target)
83 }
84}
85
86impl<R: RealScalar> Problem for HSProblem<R> {
87 fn num_params(&self) -> usize {
88 self.circuit.num_params()
89 }
90}
91
92impl<R: RealScalar> ProvidesCostFunction<R> for HSProblem<R> {
93 type CostFunction = HSFunction<R, 0>;
94
95 fn build_cost_function(&self) -> Self::CostFunction {
96 HSFunction {
97 tnvm: self.build_cost(),
98 n: R::from64(4.0),
99 } }
101}
102
103impl<R: RealScalar> ProvidesResidualFunction<R> for HSProblem<R> {
104 type ResidualFunction = HSFunction<R, 0>;
105
106 fn build_residual_function(&self) -> Self::ResidualFunction {
107 HSFunction {
108 tnvm: self.build_residual(),
109 n: R::from64(4.0),
110 }
111 }
112}
113
114impl<R: RealScalar> ProvidesJacobian<R> for HSProblem<R> {
115 type Jacobian = HSFunction<R, GRADIENT>;
116
117 fn build_jacobian(&self) -> Self::Jacobian {
118 HSFunction {
119 tnvm: self.build_residual(),
120 n: R::from64(4.0),
121 }
122 }
123}
124
125pub struct HSFunction<R: RealScalar, const D: DifferentiationLevel> {
126 tnvm: PinnedTNVM<R::C, D>,
127 n: R,
128}
129
130impl<R: RealScalar, const D: DifferentiationLevel> Function for HSFunction<R, D> {
131 fn num_params(&self) -> usize {
132 self.tnvm.num_params()
133 }
135}
136
137use num_complex::ComplexFloat;
138
139impl<R: RealScalar, const D: DifferentiationLevel> CostFunction<R> for HSFunction<R, D> {
140 fn cost(&mut self, params: &[R]) -> R {
141 let result = self.tnvm.evaluate::<FUNCTION>(params);
142 let trace = result.get_fn_result().unpack_scalar(); let inner = trace.abs() / self.n;
145 R::from64(1.0) - inner
146 }
150}
151
152impl<R: RealScalar, const D: DifferentiationLevel> Gradient<R> for HSFunction<R, D> {
154 fn gradient_into(&mut self, params: &[R], grad_out: RowMut<R>) {
155 self.cost_and_gradient_into(params, grad_out);
156 }
157
158 fn cost_and_gradient_into(&mut self, params: &[R], grad_out: RowMut<R>) -> R {
159 let result = self.tnvm.evaluate::<GRADIENT>(params);
160 let grad_trace = result.get_grad_result().unpack_vector();
161 let trace = result.get_fn_result().unpack_scalar();
162 let trace_re = trace.real();
163 let trace_im = trace.imag();
164 for (out, trace) in grad_out.iter_mut().zip(grad_trace.iter()) {
165 let trace_re_d = trace.real();
166 let trace_im_d = trace.imag();
167 let num = trace_re * trace_re_d + trace_im * trace_im_d;
168 let dem = self.n * trace.abs();
169 *out = -(num / dem)
170 }
171 R::from64(1.0) - (trace.abs() / self.n)
172 }
173}
174
175impl<R: RealScalar, const D: DifferentiationLevel> Hessian<R> for HSFunction<R, D> {
176 fn hessian_into(&mut self, _params: &[R], _hess_out: faer::MatMut<R>) {
177 todo!()
178 }
179
180 fn cost_gradient_and_hessian_into(
181 &mut self,
182 _params: &[R],
183 _grad_out: RowMut<R>,
184 _hess_out: faer::MatMut<R>,
185 ) -> R {
186 todo!("Need better iterators")
194 }
203}
204
205impl<R: RealScalar, const D: DifferentiationLevel> ResidualFunction<R> for HSFunction<R, D> {
206 fn num_residuals(&self) -> usize {
207 let out_shape = self.tnvm.out_shape();
208 (out_shape.num_elements() - out_shape.nmats()) * 2
209 }
210
211 fn residuals_into(&mut self, params: &[R], mut residuals_out: ColMut<R>) {
212 let result = self.tnvm.evaluate::<FUNCTION>(params);
213 let kraus_ops = result.get_fn_result2().unpack_tensor3d();
214
215 let mut residual_index = 0;
216 for k in 0..kraus_ops.dims()[0] {
217 let mat = kraus_ops.subtensor_ref(k);
218
219 for (j, col) in mat.col_iter().enumerate() {
220 for (i, elem) in col.iter().enumerate() {
221 if i == j {
222 continue;
223 }
224 unsafe {
225 let out = residuals_out.rb_mut().get_mut_unchecked(residual_index);
226 *out = elem.real();
227 residual_index += 1;
228 let out = residuals_out.rb_mut().get_mut_unchecked(residual_index);
229 *out = elem.imag();
230 residual_index += 1;
231 }
232 }
233 }
234
235 for diag_iter in 0..(mat.nrows() - 1) {
236 unsafe {
237 let d_i = mat.get_unchecked(diag_iter, diag_iter);
238 let d_j = mat.get_unchecked(diag_iter + 1, diag_iter + 1);
239 let out = residuals_out.rb_mut().get_mut_unchecked(residual_index);
240 *out = d_j.real() - d_i.real();
241 residual_index += 1;
242 let out = residuals_out.rb_mut().get_mut_unchecked(residual_index);
243 *out = d_j.imag() - d_i.imag();
244 residual_index += 1;
245 }
246 }
247 }
248 }
249}
250
251impl<R: RealScalar, const D: DifferentiationLevel> Jacobian<R> for HSFunction<R, D> {
252 fn residuals_and_jacobian_into(
253 &mut self,
254 params: &[R],
255 mut residuals_out: ColMut<R>,
256 mut jacobian_out: MatMut<R>,
257 ) {
258 let result = self.tnvm.evaluate::<GRADIENT>(params);
259 let kraus_ops = result.get_fn_result2().unpack_tensor3d();
260
261 let mut residual_index = 0;
274 for k in 0..kraus_ops.dims()[0] {
275 let mat = kraus_ops.subtensor_ref(k);
276
277 for (j, col) in mat.col_iter().enumerate() {
278 for (i, elem) in col.iter().enumerate() {
279 if i == j {
280 continue;
281 }
282 unsafe {
283 let out = residuals_out.rb_mut().get_mut_unchecked(residual_index);
284 *out = elem.real();
285 residual_index += 1;
286 let out = residuals_out.rb_mut().get_mut_unchecked(residual_index);
287 *out = elem.imag();
288 residual_index += 1;
289 }
290 }
291 }
292
293 for diag_iter in 0..(mat.nrows() - 1) {
294 unsafe {
295 let d_i = mat.get_unchecked(diag_iter, diag_iter);
296 let d_j = mat.get_unchecked(diag_iter + 1, diag_iter + 1);
297 let out = residuals_out.rb_mut().get_mut_unchecked(residual_index);
298 *out = d_j.real() - d_i.real();
299 residual_index += 1;
300 let out = residuals_out.rb_mut().get_mut_unchecked(residual_index);
301 *out = d_j.imag() - d_i.imag();
302 residual_index += 1;
303 }
304 }
305 }
306
307 let grad_out = result.get_grad_result2().unpack_tensor4d();
308
309 for p in 0..grad_out.dims()[0] {
310 let partial_kraus_ops = grad_out.subtensor_ref(p);
311 let mut jacobian_col_out = jacobian_out.rb_mut().col_mut(p);
312 let mut residual_index = 0;
313
314 for k in 0..partial_kraus_ops.dims()[0] {
315 let mat = partial_kraus_ops.subtensor_ref(k);
316 for (j, col) in mat.col_iter().enumerate() {
319 for (i, elem) in col.iter().enumerate() {
320 if i == j {
321 continue;
322 }
323 unsafe {
324 let out = jacobian_col_out.rb_mut().get_mut_unchecked(residual_index);
325 *out = elem.real();
326 residual_index += 1;
327 let out = jacobian_col_out.rb_mut().get_mut_unchecked(residual_index);
328 *out = elem.imag();
329 residual_index += 1;
330 }
331 }
332 }
333
334 for diag_iter in 0..(mat.nrows() - 1) {
335 unsafe {
336 let d_i = mat.get_unchecked(diag_iter, diag_iter);
337 let d_j = mat.get_unchecked(diag_iter + 1, diag_iter + 1);
338 let out = jacobian_col_out.rb_mut().get_mut_unchecked(residual_index);
339 *out = d_j.real() - d_i.real();
340 residual_index += 1;
341 let out = jacobian_col_out.rb_mut().get_mut_unchecked(residual_index);
342 *out = d_j.imag() - d_i.imag();
343 residual_index += 1;
344 }
345 }
346 }
347 }
348 }
349
350 fn jacobian_into(&mut self, params: &[R], mut jacobian_out: MatMut<R>) {
351 let result = self.tnvm.evaluate::<GRADIENT>(params);
352 let grad_out = result.get_grad_result2().unpack_tensor4d();
353
354 for p in 0..grad_out.dims()[0] {
355 let partial_kraus_ops = grad_out.subtensor_ref(p);
356 let mut jacobian_col_out = jacobian_out.rb_mut().col_mut(p);
357 let mut residual_index = 0;
358
359 for k in 0..partial_kraus_ops.dims()[0] {
360 let mat = partial_kraus_ops.subtensor_ref(k);
361
362 for (j, col) in mat.col_iter().enumerate() {
363 for (i, elem) in col.iter().enumerate() {
364 if i == j {
365 continue;
366 }
367 unsafe {
368 let out = jacobian_col_out.rb_mut().get_mut_unchecked(residual_index);
369 *out = elem.real();
370 residual_index += 1;
371 let out = jacobian_col_out.rb_mut().get_mut_unchecked(residual_index);
372 *out = elem.imag();
373 residual_index += 1;
374 }
375 }
376 }
377
378 for diag_iter in 0..(mat.nrows() - 1) {
379 unsafe {
380 let d_i = mat.get_unchecked(diag_iter, diag_iter);
381 let d_j = mat.get_unchecked(diag_iter + 1, diag_iter + 1);
382 let out = jacobian_col_out.rb_mut().get_mut_unchecked(residual_index);
383 *out = d_j.real() - d_i.real();
384 residual_index += 1;
385 let out = jacobian_col_out.rb_mut().get_mut_unchecked(residual_index);
386 *out = d_j.imag() - d_i.imag();
387 residual_index += 1;
388 }
389 }
390 }
391 }
392 }
393}