1use oxilean_kernel::{BinderInfo, Declaration, Environment, Expr, Level, Name};
6
7use super::functions::*;
8use super::functions::{EPSILON_0, MU_0};
9
10#[derive(Debug, Clone)]
15pub struct GeodesicEquation {
16 pub metric_components: Vec<Vec<f64>>,
18}
19impl GeodesicEquation {
20 pub fn new(dim: usize) -> Self {
22 let mut g = vec![vec![0.0; dim]; dim];
23 for i in 0..dim {
24 g[i][i] = 1.0;
25 }
26 Self {
27 metric_components: g,
28 }
29 }
30 pub fn christoffel_symbol(&self, _i: usize, _j: usize, _k: usize) -> f64 {
32 0.0
33 }
34 pub fn geodesic_deviation(&self) -> f64 {
36 0.0
37 }
38}
39#[derive(Debug, Clone, PartialEq, Eq)]
41pub enum IntegratorMethod {
42 Euler,
44 Leapfrog,
46 RK4,
48 SymplecticEuler,
50}
51#[allow(dead_code)]
56#[derive(Debug, Clone)]
57pub struct BRSTComplex {
58 pub cochains: Vec<Vec<f64>>,
60 pub differentials: Vec<Vec<Vec<f64>>>,
62}
63#[allow(dead_code)]
64impl BRSTComplex {
65 pub fn new(dims: Vec<usize>) -> Self {
67 let n = dims.len();
68 let cochains = dims.iter().map(|&d| vec![0.0; d]).collect();
69 let differentials = (0..n.saturating_sub(1))
70 .map(|k| vec![vec![0.0; dims[k]]; dims[k + 1]])
71 .collect();
72 Self {
73 cochains,
74 differentials,
75 }
76 }
77 pub fn apply_differential(&self, k: usize, v: &[f64]) -> Vec<f64> {
79 if k + 1 >= self.differentials.len() + 1 {
80 return vec![];
81 }
82 let q = &self.differentials[k];
83 q.iter()
84 .map(|row| row.iter().zip(v.iter()).map(|(a, b)| a * b).sum())
85 .collect()
86 }
87 pub fn check_nilpotency(&self, k: usize, eps: f64) -> bool {
89 if k + 1 >= self.differentials.len() {
90 return true;
91 }
92 let qk = &self.differentials[k];
93 let qk1 = &self.differentials[k + 1];
94 let rows1 = qk1.len();
95 let cols1 = if rows1 > 0 {
96 qk1[0].len()
97 } else {
98 return true;
99 };
100 let cols0 = if !qk.is_empty() {
101 qk[0].len()
102 } else {
103 return true;
104 };
105 for i in 0..rows1 {
106 for j in 0..cols0 {
107 let val: f64 = (0..cols1)
108 .map(|l| {
109 qk1[i].get(l).copied().unwrap_or(0.0)
110 * qk.get(l).and_then(|r| r.get(j)).copied().unwrap_or(0.0)
111 })
112 .sum();
113 if val.abs() > eps {
114 return false;
115 }
116 }
117 }
118 true
119 }
120 pub fn euler_characteristic(&self) -> i64 {
122 self.cochains
123 .iter()
124 .enumerate()
125 .map(|(k, c)| {
126 if k % 2 == 0 {
127 c.len() as i64
128 } else {
129 -(c.len() as i64)
130 }
131 })
132 .sum()
133 }
134 pub fn dimensions(&self) -> Vec<usize> {
136 self.cochains.iter().map(|c| c.len()).collect()
137 }
138}
139#[derive(Debug, Clone)]
146pub struct KdVSoliton {
147 pub kappa: f64,
149 pub x0: f64,
151}
152impl KdVSoliton {
153 pub fn new(kappa: f64, x0: f64) -> Self {
155 assert!(kappa > 0.0, "kappa must be positive");
156 Self { kappa, x0 }
157 }
158 pub fn eval(&self, x: f64, t: f64) -> f64 {
160 let xi = 0.5 * self.kappa * (x - self.kappa * self.kappa * t - self.x0);
161 let sech = 1.0 / xi.cosh();
162 -0.5 * self.kappa * self.kappa * sech * sech
163 }
164 pub fn speed(&self) -> f64 {
166 self.kappa * self.kappa
167 }
168 pub fn amplitude(&self) -> f64 {
170 -0.5 * self.kappa * self.kappa
171 }
172 pub fn eval_grid(&self, xs: &[f64], t: f64) -> Vec<f64> {
174 xs.iter().map(|&x| self.eval(x, t)).collect()
175 }
176}
177#[derive(Debug, Clone)]
182pub struct HamiltonianSystem {
183 pub q: Vec<f64>,
185 pub p: Vec<f64>,
187 pub dt: f64,
189 pub time: f64,
191}
192impl HamiltonianSystem {
193 pub fn new(q: Vec<f64>, p: Vec<f64>, dt: f64) -> Self {
195 assert_eq!(q.len(), p.len(), "q and p must have the same dimension");
196 Self {
197 q,
198 p,
199 dt,
200 time: 0.0,
201 }
202 }
203 pub fn step(&mut self, grad_h: &dyn Fn(&[f64], &[f64]) -> (Vec<f64>, Vec<f64>)) {
207 let integrator = SymplecticIntegrator {
208 dt: self.dt,
209 method: IntegratorMethod::SymplecticEuler,
210 };
211 let (q_new, p_new) = integrator.step(&self.q, &self.p, grad_h);
212 self.q = q_new;
213 self.p = p_new;
214 self.time += self.dt;
215 }
216 pub fn run(
218 &mut self,
219 n: usize,
220 grad_h: &dyn Fn(&[f64], &[f64]) -> (Vec<f64>, Vec<f64>),
221 ) -> (Vec<Vec<f64>>, Vec<Vec<f64>>) {
222 let mut qs = Vec::with_capacity(n);
223 let mut ps = Vec::with_capacity(n);
224 for _ in 0..n {
225 self.step(grad_h);
226 qs.push(self.q.clone());
227 ps.push(self.p.clone());
228 }
229 (qs, ps)
230 }
231 pub fn kinetic_energy(&self, masses: &[f64]) -> f64 {
234 self.p
235 .iter()
236 .zip(masses.iter())
237 .map(|(&pi, &mi)| pi * pi / (2.0 * mi))
238 .sum()
239 }
240}
241#[derive(Debug, Clone)]
243pub struct ClassicalParticle {
244 pub mass: f64,
246 pub position: Vec<f64>,
248 pub velocity: Vec<f64>,
250}
251impl ClassicalParticle {
252 pub fn new(mass: f64, dim: usize) -> Self {
254 Self {
255 mass,
256 position: vec![0.0; dim],
257 velocity: vec![0.0; dim],
258 }
259 }
260 pub fn kinetic_energy(&self) -> f64 {
262 let v_sq: f64 = self.velocity.iter().map(|&vi| vi * vi).sum();
263 0.5 * self.mass * v_sq
264 }
265 pub fn set_position(&mut self, pos: Vec<f64>) {
267 self.position = pos;
268 }
269 pub fn set_velocity(&mut self, vel: Vec<f64>) {
271 self.velocity = vel;
272 }
273 pub fn momentum(&self) -> Vec<f64> {
275 self.velocity.iter().map(|&vi| self.mass * vi).collect()
276 }
277}
278#[allow(dead_code)]
283#[derive(Debug, Clone)]
284pub struct PathIntegralMonteCarlo {
285 pub n_slices: usize,
287 pub tau: f64,
289 pub mass: f64,
291 pub path: Vec<f64>,
293}
294#[allow(dead_code)]
295impl PathIntegralMonteCarlo {
296 pub fn new(n_slices: usize, beta: f64, mass: f64) -> Self {
298 Self {
299 n_slices,
300 tau: beta / (n_slices as f64),
301 mass,
302 path: vec![0.0; n_slices],
303 }
304 }
305 pub fn kinetic_action(&self) -> f64 {
308 let prefactor = self.mass / (2.0 * self.tau);
309 let n = self.n_slices;
310 (0..n)
311 .map(|k| {
312 let diff = self.path[(k + 1) % n] - self.path[k];
313 prefactor * diff * diff
314 })
315 .sum()
316 }
317 pub fn potential_action_harmonic(&self, omega: f64) -> f64 {
320 self.tau
321 * self
322 .path
323 .iter()
324 .map(|&x| 0.5 * omega * omega * x * x)
325 .sum::<f64>()
326 }
327 pub fn total_action_harmonic(&self, omega: f64) -> f64 {
329 self.kinetic_action() + self.potential_action_harmonic(omega)
330 }
331 pub fn virial_energy_estimate(&self, omega: f64) -> f64 {
334 let x_sq: f64 = self.path.iter().map(|&x| x * x).sum::<f64>() / (self.n_slices as f64);
335 0.5 * omega * omega * x_sq
336 }
337 pub fn set_path(&mut self, path: Vec<f64>) {
339 assert_eq!(path.len(), self.n_slices);
340 self.path = path;
341 }
342 pub fn winding_number(&self) -> f64 {
344 let first = self.path[0];
345 let last = self.path[self.n_slices - 1];
346 (last - first) / (2.0 * std::f64::consts::PI)
347 }
348}
349#[derive(Debug, Clone)]
357pub struct SchrodingerPropagator {
358 pub n_grid: usize,
360 pub dx: f64,
362 pub mass: f64,
364 pub dt: f64,
366 pub potential: Vec<f64>,
368}
369impl SchrodingerPropagator {
370 pub fn new(n_grid: usize, dx: f64, mass: f64, dt: f64, potential: Vec<f64>) -> Self {
372 assert_eq!(
373 potential.len(),
374 n_grid,
375 "potential length must equal n_grid"
376 );
377 Self {
378 n_grid,
379 dx,
380 mass,
381 dt,
382 potential,
383 }
384 }
385 pub fn kinetic_half_step(&self, psi_re: &[f64], psi_im: &[f64]) -> (Vec<f64>, Vec<f64>) {
392 let coeff = self.dt / (2.0 * self.mass * self.dx * self.dx);
393 let n = self.n_grid;
394 let mut re_out = vec![0.0; n];
395 let mut im_out = vec![0.0; n];
396 for i in 0..n {
397 let left = if i == 0 { 0.0 } else { 1.0 };
398 let right = if i + 1 == n { 0.0 } else { 1.0 };
399 let lap_re = left * psi_re[i.saturating_sub(1)] - 2.0 * psi_re[i]
400 + right * psi_re[(i + 1).min(n - 1)];
401 let lap_im = left * psi_im[i.saturating_sub(1)] - 2.0 * psi_im[i]
402 + right * psi_im[(i + 1).min(n - 1)];
403 re_out[i] = psi_re[i] - coeff * lap_im;
404 im_out[i] = psi_im[i] + coeff * lap_re;
405 }
406 (re_out, im_out)
407 }
408 pub fn potential_half_step(&self, psi_re: &[f64], psi_im: &[f64]) -> (Vec<f64>, Vec<f64>) {
410 let mut re_out = vec![0.0; self.n_grid];
411 let mut im_out = vec![0.0; self.n_grid];
412 for i in 0..self.n_grid {
413 let phase = -self.potential[i] * self.dt / 2.0;
414 let (s, c) = phase.sin_cos();
415 re_out[i] = psi_re[i] * c - psi_im[i] * s;
416 im_out[i] = psi_re[i] * s + psi_im[i] * c;
417 }
418 (re_out, im_out)
419 }
420 pub fn step(&self, psi_re: &[f64], psi_im: &[f64]) -> (Vec<f64>, Vec<f64>) {
422 let (r1, i1) = self.potential_half_step(psi_re, psi_im);
423 let (r2, i2) = self.kinetic_half_step(&r1, &i1);
424 self.potential_half_step(&r2, &i2)
425 }
426 pub fn norm_squared(&self, psi_re: &[f64], psi_im: &[f64]) -> f64 {
428 psi_re
429 .iter()
430 .zip(psi_im.iter())
431 .map(|(&r, &i)| (r * r + i * i) * self.dx)
432 .sum()
433 }
434}
435#[derive(Debug, Clone, PartialEq, Eq)]
440pub struct FockSpaceVec {
441 pub occupations: Vec<u64>,
443}
444impl FockSpaceVec {
445 pub fn vacuum(n_modes: usize) -> Self {
447 Self {
448 occupations: vec![0; n_modes],
449 }
450 }
451 pub fn total_number(&self) -> u64 {
453 self.occupations.iter().sum()
454 }
455 pub fn apply_creation(&self, k: usize) -> Option<(Self, f64)> {
459 if k >= self.occupations.len() {
460 return None;
461 }
462 let mut new_occ = self.occupations.clone();
463 let n_k = new_occ[k];
464 new_occ[k] += 1;
465 let norm = ((n_k + 1) as f64).sqrt();
466 Some((
467 Self {
468 occupations: new_occ,
469 },
470 norm,
471 ))
472 }
473 pub fn apply_annihilation(&self, k: usize) -> Option<(Self, f64)> {
477 if k >= self.occupations.len() || self.occupations[k] == 0 {
478 return None;
479 }
480 let mut new_occ = self.occupations.clone();
481 let n_k = new_occ[k];
482 new_occ[k] -= 1;
483 let norm = (n_k as f64).sqrt();
484 Some((
485 Self {
486 occupations: new_occ,
487 },
488 norm,
489 ))
490 }
491 pub fn number_op(&self, k: usize) -> u64 {
493 self.occupations.get(k).copied().unwrap_or(0)
494 }
495 pub fn inner_product(&self, other: &FockSpaceVec) -> f64 {
497 if self.occupations == other.occupations {
498 1.0
499 } else {
500 0.0
501 }
502 }
503}
504#[derive(Debug, Clone)]
506pub struct ElectromagneticField {
507 pub e: Vec<f64>,
509 pub b: Vec<f64>,
511}
512impl ElectromagneticField {
513 pub fn new() -> Self {
515 Self {
516 e: vec![0.0; 3],
517 b: vec![0.0; 3],
518 }
519 }
520 pub fn energy_density(&self) -> f64 {
522 let e_sq: f64 = self.e.iter().map(|&ei| ei * ei).sum();
523 let b_sq: f64 = self.b.iter().map(|&bi| bi * bi).sum();
524 (EPSILON_0 * e_sq + b_sq / MU_0) / 2.0
525 }
526 pub fn poynting_vector(&self) -> Vec<f64> {
528 let cross = cross3(&self.e, &self.b);
529 cross.into_iter().map(|c| c / MU_0).collect()
530 }
531 pub fn lorentz_force(&self, q: f64, v: &[f64]) -> Vec<f64> {
533 let vxb = cross3(v, &self.b);
534 (0..3).map(|i| q * (self.e[i] + vxb[i])).collect()
535 }
536}
537#[allow(dead_code)]
542#[derive(Debug, Clone)]
543pub struct MirrorSymmetryData {
544 pub complex_dim: usize,
546 pub hodge_x: Vec<Vec<u64>>,
548 pub hodge_y: Vec<Vec<u64>>,
550}
551#[allow(dead_code)]
552impl MirrorSymmetryData {
553 pub fn new(complex_dim: usize) -> Self {
555 let n = complex_dim + 1;
556 Self {
557 complex_dim,
558 hodge_x: vec![vec![0; n]; n],
559 hodge_y: vec![vec![0; n]; n],
560 }
561 }
562 pub fn set_hodge_x(&mut self, p: usize, q: usize, val: u64) {
564 self.hodge_x[p][q] = val;
565 }
566 pub fn set_hodge_y(&mut self, p: usize, q: usize, val: u64) {
568 self.hodge_y[p][q] = val;
569 }
570 pub fn verify_mirror(&self) -> bool {
572 let n = self.complex_dim;
573 for p in 0..=n {
574 for q in 0..=n {
575 if n < p {
576 continue;
577 }
578 let mirror_p = n - p;
579 if self.hodge_x[p][q] != self.hodge_y[mirror_p][q] {
580 return false;
581 }
582 }
583 }
584 true
585 }
586 pub fn euler_characteristic_x(&self) -> i64 {
588 self.hodge_x
589 .iter()
590 .enumerate()
591 .flat_map(|(p, row)| {
592 row.iter().enumerate().map(move |(q, &h)| {
593 if (p + q) % 2 == 0 {
594 h as i64
595 } else {
596 -(h as i64)
597 }
598 })
599 })
600 .sum()
601 }
602 pub fn h11_x(&self) -> u64 {
604 if self.complex_dim >= 1 {
605 self.hodge_x[1][1]
606 } else {
607 0
608 }
609 }
610 pub fn h11_y(&self) -> u64 {
612 if self.complex_dim >= 1 {
613 self.hodge_y[1][1]
614 } else {
615 0
616 }
617 }
618}
619#[derive(Debug, Clone)]
621pub struct SymplecticIntegrator {
622 pub dt: f64,
624 pub method: IntegratorMethod,
626}
627impl SymplecticIntegrator {
628 pub fn step(
633 &self,
634 q: &[f64],
635 p: &[f64],
636 grad_h: &dyn Fn(&[f64], &[f64]) -> (Vec<f64>, Vec<f64>),
637 ) -> (Vec<f64>, Vec<f64>) {
638 let dt = self.dt;
639 match self.method {
640 IntegratorMethod::Euler => {
641 let (dh_dp, dh_dq) = grad_h(q, p);
642 let q_new: Vec<f64> = q
643 .iter()
644 .zip(&dh_dp)
645 .map(|(&qi, &di)| qi + dt * di)
646 .collect();
647 let p_new: Vec<f64> = p
648 .iter()
649 .zip(&dh_dq)
650 .map(|(&pi, &di)| pi - dt * di)
651 .collect();
652 (q_new, p_new)
653 }
654 IntegratorMethod::SymplecticEuler => {
655 let (_, dh_dq) = grad_h(q, p);
656 let p_new: Vec<f64> = p
657 .iter()
658 .zip(&dh_dq)
659 .map(|(&pi, &di)| pi - dt * di)
660 .collect();
661 let (dh_dp_new, _) = grad_h(q, &p_new);
662 let q_new: Vec<f64> = q
663 .iter()
664 .zip(&dh_dp_new)
665 .map(|(&qi, &di)| qi + dt * di)
666 .collect();
667 (q_new, p_new)
668 }
669 IntegratorMethod::Leapfrog => {
670 let (_, dh_dq) = grad_h(q, p);
671 let p_half: Vec<f64> = p
672 .iter()
673 .zip(&dh_dq)
674 .map(|(&pi, &di)| pi - 0.5 * dt * di)
675 .collect();
676 let (dh_dp_half, _) = grad_h(q, &p_half);
677 let q_new: Vec<f64> = q
678 .iter()
679 .zip(&dh_dp_half)
680 .map(|(&qi, &di)| qi + dt * di)
681 .collect();
682 let (_, dh_dq_new) = grad_h(&q_new, &p_half);
683 let p_new: Vec<f64> = p_half
684 .iter()
685 .zip(&dh_dq_new)
686 .map(|(&pi, &di)| pi - 0.5 * dt * di)
687 .collect();
688 (q_new, p_new)
689 }
690 IntegratorMethod::RK4 => {
691 let f = |q: &[f64], p: &[f64]| -> (Vec<f64>, Vec<f64>) {
692 let (dh_dp, dh_dq) = grad_h(q, p);
693 let dq: Vec<f64> = dh_dp;
694 let dp: Vec<f64> = dh_dq.iter().map(|&d| -d).collect();
695 (dq, dp)
696 };
697 let (k1q, k1p) = f(q, p);
698 let q1: Vec<f64> = q
699 .iter()
700 .zip(&k1q)
701 .map(|(&qi, &ki)| qi + 0.5 * dt * ki)
702 .collect();
703 let p1: Vec<f64> = p
704 .iter()
705 .zip(&k1p)
706 .map(|(&pi, &ki)| pi + 0.5 * dt * ki)
707 .collect();
708 let (k2q, k2p) = f(&q1, &p1);
709 let q2: Vec<f64> = q
710 .iter()
711 .zip(&k2q)
712 .map(|(&qi, &ki)| qi + 0.5 * dt * ki)
713 .collect();
714 let p2: Vec<f64> = p
715 .iter()
716 .zip(&k2p)
717 .map(|(&pi, &ki)| pi + 0.5 * dt * ki)
718 .collect();
719 let (k3q, k3p) = f(&q2, &p2);
720 let q3: Vec<f64> = q.iter().zip(&k3q).map(|(&qi, &ki)| qi + dt * ki).collect();
721 let p3: Vec<f64> = p.iter().zip(&k3p).map(|(&pi, &ki)| pi + dt * ki).collect();
722 let (k4q, k4p) = f(&q3, &p3);
723 let q_new: Vec<f64> = q
724 .iter()
725 .enumerate()
726 .map(|(i, &qi)| qi + dt / 6.0 * (k1q[i] + 2.0 * k2q[i] + 2.0 * k3q[i] + k4q[i]))
727 .collect();
728 let p_new: Vec<f64> = p
729 .iter()
730 .enumerate()
731 .map(|(i, &pi)| pi + dt / 6.0 * (k1p[i] + 2.0 * k2p[i] + 2.0 * k3p[i] + k4p[i]))
732 .collect();
733 (q_new, p_new)
734 }
735 }
736 }
737}
738#[derive(Debug, Clone)]
745pub struct GaugeField {
746 pub size: usize,
748 pub links: Vec<Vec<f64>>,
750 pub beta: f64,
752}
753impl GaugeField {
754 pub fn new_cold(size: usize, beta: f64) -> Self {
756 let n = size * size;
757 Self {
758 size,
759 links: vec![vec![0.0; n], vec![0.0; n]],
760 beta,
761 }
762 }
763 fn site(&self, i: usize, j: usize) -> usize {
765 (i % self.size) * self.size + (j % self.size)
766 }
767 pub fn plaquette_angle(&self, i: usize, j: usize) -> f64 {
770 let s00 = self.site(i, j);
771 let s10 = self.site(i + 1, j);
772 let s01 = self.site(i, j + 1);
773 self.links[0][s00] + self.links[1][s10] - self.links[0][s01] - self.links[1][s00]
774 }
775 pub fn action(&self) -> f64 {
777 let n = self.size;
778 let mut s = 0.0;
779 for i in 0..n {
780 for j in 0..n {
781 s += 1.0 - self.plaquette_angle(i, j).cos();
782 }
783 }
784 self.beta * s
785 }
786 pub fn polyakov_loop(&self, j: usize) -> f64 {
788 let angle: f64 = (0..self.size).map(|i| self.links[0][self.site(i, j)]).sum();
789 angle.cos()
790 }
791}
792#[allow(dead_code)]
798#[derive(Debug, Clone)]
799pub struct SupersymmetricOscillator {
800 pub omega: f64,
802}
803#[allow(dead_code)]
804impl SupersymmetricOscillator {
805 pub fn new(omega: f64) -> Self {
807 assert!(omega > 0.0, "omega must be positive");
808 Self { omega }
809 }
810 pub fn energy(&self, n_b: u64, _n_f: u64) -> f64 {
812 self.omega * (n_b as f64)
813 }
814 pub fn degeneracy(&self, n: u64) -> u64 {
816 if n == 0 {
817 1
818 } else {
819 2
820 }
821 }
822 pub fn witten_index(&self) -> i64 {
824 1
825 }
826 pub fn partition_function(&self, beta: f64, n_max: u64) -> f64 {
828 (0..=n_max)
829 .map(|n| {
830 let e = self.energy(n, 0);
831 (self.degeneracy(n) as f64) * (-beta * e).exp()
832 })
833 .sum()
834 }
835 pub fn ground_state_energy(&self) -> f64 {
837 0.0
838 }
839}
840#[allow(dead_code)]
845#[derive(Debug, Clone)]
846pub struct DonaldsonWitten {
847 pub manifold_name: String,
849 pub signature: i64,
851 pub euler_char: i64,
853 pub invariants: Vec<(u64, f64)>,
855}
856#[allow(dead_code)]
857impl DonaldsonWitten {
858 pub fn new(name: impl Into<String>, signature: i64, euler_char: i64) -> Self {
860 Self {
861 manifold_name: name.into(),
862 signature,
863 euler_char,
864 invariants: Vec::new(),
865 }
866 }
867 pub fn virtual_dimension(&self, k: u64) -> i64 {
870 let b2_plus = (self.euler_char + self.signature) / 2;
871 let b1 = 0i64;
872 8 * (k as i64) - 3 * (1 + b1 - b2_plus)
873 }
874 pub fn add_invariant(&mut self, k: u64, value: f64) {
876 self.invariants.push((k, value));
877 }
878 pub fn get_invariant(&self, k: u64) -> Option<f64> {
880 self.invariants
881 .iter()
882 .find(|&&(kk, _)| kk == k)
883 .map(|&(_, v)| v)
884 }
885 pub fn b2_plus(&self) -> i64 {
887 (self.euler_char + self.signature) / 2
888 }
889 pub fn b2_minus(&self) -> i64 {
891 (self.euler_char - self.signature) / 2
892 }
893}