1use crate::error::Result;
9use scirs2_core::ndarray::{s, Array1, Array2, Array3, ArrayD, IxDyn};
10use serde::{Deserialize, Serialize};
11use std::collections::HashMap;
12
13#[derive(Debug, Clone, Serialize, Deserialize)]
15pub struct QPINNConfig {
16 pub num_qubits: usize,
18 pub num_layers: usize,
20 pub domain_bounds: Vec<(f64, f64)>,
22 pub time_bounds: (f64, f64),
24 pub equation_type: PhysicsEquationType,
26 pub boundary_conditions: Vec<BoundaryCondition>,
28 pub initial_conditions: Vec<InitialCondition>,
30 pub loss_weights: LossWeights,
32 pub ansatz_config: AnsatzConfig,
34 pub training_config: TrainingConfig,
36 pub physics_constraints: PhysicsConstraints,
38}
39
40impl Default for QPINNConfig {
41 fn default() -> Self {
42 Self {
43 num_qubits: 6,
44 num_layers: 4,
45 domain_bounds: vec![(-1.0, 1.0), (-1.0, 1.0)],
46 time_bounds: (0.0, 1.0),
47 equation_type: PhysicsEquationType::Poisson,
48 boundary_conditions: vec![],
49 initial_conditions: vec![],
50 loss_weights: LossWeights::default(),
51 ansatz_config: AnsatzConfig::default(),
52 training_config: TrainingConfig::default(),
53 physics_constraints: PhysicsConstraints::default(),
54 }
55 }
56}
57
58#[derive(Debug, Clone, Serialize, Deserialize)]
60pub enum PhysicsEquationType {
61 Poisson,
63 Heat,
65 Wave,
67 Schrodinger,
69 NavierStokes,
71 Maxwell,
73 KleinGordon,
75 Burgers,
77 Custom {
79 differential_operator: String,
80 equation_form: String,
81 },
82}
83
84#[derive(Debug, Clone, Serialize, Deserialize)]
86pub struct BoundaryCondition {
87 pub boundary: BoundaryLocation,
89 pub condition_type: BoundaryType,
91 pub value_function: String, }
94
95#[derive(Debug, Clone, Serialize, Deserialize)]
97pub enum BoundaryLocation {
98 Left,
100 Right,
102 Bottom,
104 Top,
106 Custom(String),
108}
109
110#[derive(Debug, Clone, Serialize, Deserialize)]
112pub enum BoundaryType {
113 Dirichlet,
115 Neumann,
117 Robin { alpha: f64, beta: f64 },
119 Periodic,
121}
122
123#[derive(Debug, Clone, Serialize, Deserialize)]
125pub struct InitialCondition {
126 pub value_function: String,
128 pub derivative_function: Option<String>,
130}
131
132#[derive(Debug, Clone, Serialize, Deserialize)]
134pub struct LossWeights {
135 pub pde_loss_weight: f64,
137 pub boundary_loss_weight: f64,
139 pub initial_loss_weight: f64,
141 pub physics_constraint_weight: f64,
143 pub data_loss_weight: f64,
145}
146
147impl Default for LossWeights {
148 fn default() -> Self {
149 Self {
150 pde_loss_weight: 1.0,
151 boundary_loss_weight: 10.0,
152 initial_loss_weight: 10.0,
153 physics_constraint_weight: 1.0,
154 data_loss_weight: 1.0,
155 }
156 }
157}
158
159#[derive(Debug, Clone, Serialize, Deserialize)]
161pub struct AnsatzConfig {
162 pub ansatz_type: QuantumAnsatzType,
164 pub entanglement_pattern: EntanglementPattern,
166 pub repetitions: usize,
168 pub parameter_init: ParameterInitialization,
170}
171
172impl Default for AnsatzConfig {
173 fn default() -> Self {
174 Self {
175 ansatz_type: QuantumAnsatzType::EfficientSU2,
176 entanglement_pattern: EntanglementPattern::Linear,
177 repetitions: 3,
178 parameter_init: ParameterInitialization::Random,
179 }
180 }
181}
182
183#[derive(Debug, Clone, Serialize, Deserialize)]
185pub enum QuantumAnsatzType {
186 EfficientSU2,
188 TwoLocal,
190 AlternatingOperator,
192 HardwareEfficient,
194 PhysicsInformed,
196}
197
198#[derive(Debug, Clone, Serialize, Deserialize)]
200pub enum EntanglementPattern {
201 Linear,
203 Circular,
205 Full,
207 Custom(Vec<(usize, usize)>),
209}
210
211#[derive(Debug, Clone, Serialize, Deserialize)]
213pub enum ParameterInitialization {
214 Random,
216 Xavier,
218 He,
220 PhysicsInformed,
222 Custom(Vec<f64>),
224}
225
226#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct TrainingConfig {
229 pub epochs: usize,
231 pub learning_rate: f64,
233 pub optimizer: OptimizerType,
235 pub batch_size: usize,
237 pub num_collocation_points: usize,
239 pub adaptive_sampling: bool,
241}
242
243impl Default for TrainingConfig {
244 fn default() -> Self {
245 Self {
246 epochs: 1000,
247 learning_rate: 0.001,
248 optimizer: OptimizerType::Adam,
249 batch_size: 128,
250 num_collocation_points: 1000,
251 adaptive_sampling: true,
252 }
253 }
254}
255
256#[derive(Debug, Clone, Serialize, Deserialize)]
258pub enum OptimizerType {
259 Adam,
260 LBFGS,
261 SGD,
262 QuantumNaturalGradient,
263 ParameterShift,
264}
265
266#[derive(Debug, Clone, Serialize, Deserialize)]
268pub struct PhysicsConstraints {
269 pub conservation_laws: Vec<ConservationLaw>,
271 pub symmetries: Vec<Symmetry>,
273 pub solution_bounds: Option<(f64, f64)>,
275 pub energy_constraints: Vec<EnergyConstraint>,
277}
278
279impl Default for PhysicsConstraints {
280 fn default() -> Self {
281 Self {
282 conservation_laws: vec![],
283 symmetries: vec![],
284 solution_bounds: None,
285 energy_constraints: vec![],
286 }
287 }
288}
289
290#[derive(Debug, Clone, Serialize, Deserialize)]
292pub enum ConservationLaw {
293 Mass,
295 Momentum,
297 Energy,
299 Charge,
301 Custom(String),
303}
304
305#[derive(Debug, Clone, Serialize, Deserialize)]
307pub enum Symmetry {
308 Translational,
310 Rotational,
312 Reflection,
314 TimeReversal,
316 Custom(String),
318}
319
320#[derive(Debug, Clone, Serialize, Deserialize)]
322pub struct EnergyConstraint {
323 pub constraint_type: EnergyConstraintType,
325 pub target_value: f64,
327 pub tolerance: f64,
329}
330
331#[derive(Debug, Clone, Serialize, Deserialize)]
333pub enum EnergyConstraintType {
334 Total,
336 Kinetic,
338 Potential,
340 Custom(String),
342}
343
344#[derive(Debug, Clone)]
346pub struct QuantumPINN {
347 config: QPINNConfig,
348 quantum_circuit: QuantumCircuit,
349 parameters: Array1<f64>,
350 collocation_points: Array2<f64>,
351 training_history: Vec<TrainingMetrics>,
352 physics_evaluator: PhysicsEvaluator,
353}
354
355#[derive(Debug, Clone)]
357pub struct QuantumCircuit {
358 gates: Vec<QuantumGate>,
359 num_qubits: usize,
360 parameter_map: HashMap<usize, usize>, }
362
363#[derive(Debug, Clone)]
365pub struct QuantumGate {
366 gate_type: GateType,
367 qubits: Vec<usize>,
368 parameters: Vec<usize>, is_parametric: bool,
370}
371
372#[derive(Debug, Clone)]
374pub enum GateType {
375 RX,
376 RY,
377 RZ,
378 CNOT,
379 CZ,
380 CY,
381 Hadamard,
382 S,
383 T,
384 Custom(String),
385}
386
387#[derive(Debug, Clone)]
389pub struct PhysicsEvaluator {
390 equation_type: PhysicsEquationType,
391 differential_operators: HashMap<String, DifferentialOperator>,
392}
393
394#[derive(Debug, Clone)]
396pub struct DifferentialOperator {
397 operator_type: OperatorType,
398 order: usize,
399 direction: Vec<usize>, }
401
402#[derive(Debug, Clone)]
404pub enum OperatorType {
405 Gradient,
406 Laplacian,
407 Divergence,
408 Curl,
409 TimeDerivative,
410 Mixed,
411}
412
413#[derive(Debug, Clone)]
415pub struct TrainingMetrics {
416 epoch: usize,
417 total_loss: f64,
418 pde_loss: f64,
419 boundary_loss: f64,
420 initial_loss: f64,
421 physics_constraint_loss: f64,
422 quantum_fidelity: f64,
423 solution_energy: f64,
424}
425
426impl QuantumPINN {
427 pub fn new(config: QPINNConfig) -> Result<Self> {
429 let quantum_circuit = Self::build_quantum_circuit(&config)?;
430 let num_parameters = Self::count_parameters(&quantum_circuit);
431 let parameters = Self::initialize_parameters(&config, num_parameters)?;
432 let collocation_points = Self::generate_collocation_points(&config)?;
433 let physics_evaluator = PhysicsEvaluator::new(&config.equation_type)?;
434
435 Ok(Self {
436 config,
437 quantum_circuit,
438 parameters,
439 collocation_points,
440 training_history: Vec::new(),
441 physics_evaluator,
442 })
443 }
444
445 fn build_quantum_circuit(config: &QPINNConfig) -> Result<QuantumCircuit> {
447 let mut gates = Vec::new();
448 let mut parameter_map = HashMap::new();
449 let mut param_index = 0;
450
451 match config.ansatz_config.ansatz_type {
452 QuantumAnsatzType::EfficientSU2 => {
453 for rep in 0..config.ansatz_config.repetitions {
454 for qubit in 0..config.num_qubits {
456 gates.push(QuantumGate {
458 gate_type: GateType::RY,
459 qubits: vec![qubit],
460 parameters: vec![param_index],
461 is_parametric: true,
462 });
463 parameter_map.insert(gates.len() - 1, param_index);
464 param_index += 1;
465
466 gates.push(QuantumGate {
468 gate_type: GateType::RZ,
469 qubits: vec![qubit],
470 parameters: vec![param_index],
471 is_parametric: true,
472 });
473 parameter_map.insert(gates.len() - 1, param_index);
474 param_index += 1;
475 }
476
477 match config.ansatz_config.entanglement_pattern {
479 EntanglementPattern::Linear => {
480 for qubit in 0..config.num_qubits - 1 {
481 gates.push(QuantumGate {
482 gate_type: GateType::CNOT,
483 qubits: vec![qubit, qubit + 1],
484 parameters: vec![],
485 is_parametric: false,
486 });
487 }
488 }
489 EntanglementPattern::Circular => {
490 for qubit in 0..config.num_qubits {
491 gates.push(QuantumGate {
492 gate_type: GateType::CNOT,
493 qubits: vec![qubit, (qubit + 1) % config.num_qubits],
494 parameters: vec![],
495 is_parametric: false,
496 });
497 }
498 }
499 EntanglementPattern::Full => {
500 for i in 0..config.num_qubits {
501 for j in i + 1..config.num_qubits {
502 gates.push(QuantumGate {
503 gate_type: GateType::CNOT,
504 qubits: vec![i, j],
505 parameters: vec![],
506 is_parametric: false,
507 });
508 }
509 }
510 }
511 _ => {
512 return Err(crate::error::MLError::InvalidConfiguration(
513 "Unsupported entanglement pattern".to_string(),
514 ));
515 }
516 }
517 }
518 }
519 QuantumAnsatzType::PhysicsInformed => {
520 gates = Self::build_physics_informed_ansatz(
522 config,
523 &mut param_index,
524 &mut parameter_map,
525 )?;
526 }
527 _ => {
528 return Err(crate::error::MLError::InvalidConfiguration(
529 "Ansatz type not implemented".to_string(),
530 ));
531 }
532 }
533
534 Ok(QuantumCircuit {
535 gates,
536 num_qubits: config.num_qubits,
537 parameter_map,
538 })
539 }
540
541 fn build_physics_informed_ansatz(
543 config: &QPINNConfig,
544 param_index: &mut usize,
545 parameter_map: &mut HashMap<usize, usize>,
546 ) -> Result<Vec<QuantumGate>> {
547 let mut gates = Vec::new();
548
549 match config.equation_type {
550 PhysicsEquationType::Schrodinger => {
551 for layer in 0..config.num_layers {
553 for qubit in 0..config.num_qubits - 1 {
555 gates.push(QuantumGate {
556 gate_type: GateType::RX,
557 qubits: vec![qubit],
558 parameters: vec![*param_index],
559 is_parametric: true,
560 });
561 parameter_map.insert(gates.len() - 1, *param_index);
562 *param_index += 1;
563
564 gates.push(QuantumGate {
565 gate_type: GateType::CNOT,
566 qubits: vec![qubit, qubit + 1],
567 parameters: vec![],
568 is_parametric: false,
569 });
570
571 gates.push(QuantumGate {
572 gate_type: GateType::RZ,
573 qubits: vec![qubit + 1],
574 parameters: vec![*param_index],
575 is_parametric: true,
576 });
577 parameter_map.insert(gates.len() - 1, *param_index);
578 *param_index += 1;
579
580 gates.push(QuantumGate {
581 gate_type: GateType::CNOT,
582 qubits: vec![qubit, qubit + 1],
583 parameters: vec![],
584 is_parametric: false,
585 });
586 }
587
588 for qubit in 0..config.num_qubits {
590 gates.push(QuantumGate {
591 gate_type: GateType::RZ,
592 qubits: vec![qubit],
593 parameters: vec![*param_index],
594 is_parametric: true,
595 });
596 parameter_map.insert(gates.len() - 1, *param_index);
597 *param_index += 1;
598 }
599 }
600 }
601 PhysicsEquationType::Heat => {
602 for layer in 0..config.num_layers {
604 for qubit in 0..config.num_qubits {
605 gates.push(QuantumGate {
606 gate_type: GateType::RY,
607 qubits: vec![qubit],
608 parameters: vec![*param_index],
609 is_parametric: true,
610 });
611 parameter_map.insert(gates.len() - 1, *param_index);
612 *param_index += 1;
613 }
614
615 for qubit in 0..config.num_qubits - 1 {
617 gates.push(QuantumGate {
618 gate_type: GateType::CZ,
619 qubits: vec![qubit, qubit + 1],
620 parameters: vec![],
621 is_parametric: false,
622 });
623 }
624 }
625 }
626 _ => {
627 for qubit in 0..config.num_qubits {
629 gates.push(QuantumGate {
630 gate_type: GateType::RY,
631 qubits: vec![qubit],
632 parameters: vec![*param_index],
633 is_parametric: true,
634 });
635 parameter_map.insert(gates.len() - 1, *param_index);
636 *param_index += 1;
637 }
638 }
639 }
640
641 Ok(gates)
642 }
643
644 fn count_parameters(circuit: &QuantumCircuit) -> usize {
646 circuit
647 .gates
648 .iter()
649 .filter(|gate| gate.is_parametric)
650 .map(|gate| gate.parameters.len())
651 .sum()
652 }
653
654 fn initialize_parameters(config: &QPINNConfig, num_params: usize) -> Result<Array1<f64>> {
656 match &config.ansatz_config.parameter_init {
657 ParameterInitialization::Random => Ok(Array1::from_shape_fn(num_params, |_| {
658 fastrand::f64() * 2.0 * std::f64::consts::PI
659 })),
660 ParameterInitialization::Xavier => {
661 let limit = (6.0 / num_params as f64).sqrt();
662 Ok(Array1::from_shape_fn(num_params, |_| {
663 (fastrand::f64() - 0.5) * 2.0 * limit
664 }))
665 }
666 ParameterInitialization::PhysicsInformed => {
667 match config.equation_type {
669 PhysicsEquationType::Schrodinger => {
670 Ok(Array1::from_shape_fn(num_params, |_| {
672 (fastrand::f64() - 0.5) * 0.1
673 }))
674 }
675 PhysicsEquationType::Heat => {
676 Ok(Array1::from_shape_fn(num_params, |i| {
678 0.1 * (i as f64 / num_params as f64)
679 }))
680 }
681 _ => {
682 Ok(Array1::from_shape_fn(num_params, |_| {
684 fastrand::f64() * std::f64::consts::PI
685 }))
686 }
687 }
688 }
689 ParameterInitialization::Custom(values) => {
690 if values.len() != num_params {
691 return Err(crate::error::MLError::InvalidConfiguration(
692 "Custom parameter length mismatch".to_string(),
693 ));
694 }
695 Ok(Array1::from_vec(values.clone()))
696 }
697 _ => Ok(Array1::zeros(num_params)),
698 }
699 }
700
701 fn generate_collocation_points(config: &QPINNConfig) -> Result<Array2<f64>> {
703 let num_points = config.training_config.num_collocation_points;
704 let num_dims = config.domain_bounds.len() + 1; let mut points = Array2::zeros((num_points, num_dims));
706
707 for i in 0..num_points {
708 for (j, &(min_val, max_val)) in config.domain_bounds.iter().enumerate() {
710 points[[i, j]] = min_val + fastrand::f64() * (max_val - min_val);
711 }
712
713 let (t_min, t_max) = config.time_bounds;
715 points[[i, config.domain_bounds.len()]] = t_min + fastrand::f64() * (t_max - t_min);
716 }
717
718 Ok(points)
719 }
720
721 pub fn forward(&self, input_points: &Array2<f64>) -> Result<Array2<f64>> {
723 let batch_size = input_points.nrows();
724 let num_outputs = 1; let mut outputs = Array2::zeros((batch_size, num_outputs));
726
727 for i in 0..batch_size {
728 let point = input_points.row(i);
729 let quantum_state = self.encode_input(&point.to_owned())?;
730 let evolved_state = self.apply_quantum_circuit(&quantum_state)?;
731 let output = self.decode_output(&evolved_state)?;
732 outputs[[i, 0]] = output;
733 }
734
735 Ok(outputs)
736 }
737
738 fn encode_input(&self, point: &Array1<f64>) -> Result<Array1<f64>> {
740 let num_amplitudes = 1 << self.config.num_qubits;
741 let mut quantum_state = Array1::zeros(num_amplitudes);
742
743 let norm = point.iter().map(|x| x * x).sum::<f64>().sqrt();
745 if norm > 1e-10 {
746 for (i, &coord) in point.iter().enumerate() {
747 if i < num_amplitudes {
748 quantum_state[i] = coord / norm;
749 }
750 }
751 } else {
752 quantum_state[0] = 1.0;
753 }
754
755 let state_norm = quantum_state.iter().map(|x| x * x).sum::<f64>().sqrt();
757 if state_norm > 1e-10 {
758 quantum_state /= state_norm;
759 }
760
761 Ok(quantum_state)
762 }
763
764 fn apply_quantum_circuit(&self, input_state: &Array1<f64>) -> Result<Array1<f64>> {
766 let mut state = input_state.clone();
767
768 for gate in &self.quantum_circuit.gates {
769 match gate.gate_type {
770 GateType::RY => {
771 let angle = if gate.is_parametric {
772 self.parameters[gate.parameters[0]]
773 } else {
774 0.0
775 };
776 state = self.apply_ry_gate(&state, gate.qubits[0], angle)?;
777 }
778 GateType::RZ => {
779 let angle = if gate.is_parametric {
780 self.parameters[gate.parameters[0]]
781 } else {
782 0.0
783 };
784 state = self.apply_rz_gate(&state, gate.qubits[0], angle)?;
785 }
786 GateType::RX => {
787 let angle = if gate.is_parametric {
788 self.parameters[gate.parameters[0]]
789 } else {
790 0.0
791 };
792 state = self.apply_rx_gate(&state, gate.qubits[0], angle)?;
793 }
794 GateType::CNOT => {
795 state = self.apply_cnot_gate(&state, gate.qubits[0], gate.qubits[1])?;
796 }
797 GateType::CZ => {
798 state = self.apply_cz_gate(&state, gate.qubits[0], gate.qubits[1])?;
799 }
800 _ => {
801 }
803 }
804 }
805
806 Ok(state)
807 }
808
809 fn apply_rx_gate(&self, state: &Array1<f64>, qubit: usize, angle: f64) -> Result<Array1<f64>> {
811 let mut new_state = state.clone();
812 let cos_half = (angle / 2.0).cos();
813 let sin_half = (angle / 2.0).sin();
814
815 let qubit_mask = 1 << qubit;
816
817 for i in 0..state.len() {
818 if i & qubit_mask == 0 {
819 let j = i | qubit_mask;
820 if j < state.len() {
821 let state_0 = state[i];
822 let state_1 = state[j];
823 new_state[i] = cos_half * state_0 - sin_half * state_1;
824 new_state[j] = -sin_half * state_0 + cos_half * state_1;
825 }
826 }
827 }
828
829 Ok(new_state)
830 }
831
832 fn apply_ry_gate(&self, state: &Array1<f64>, qubit: usize, angle: f64) -> Result<Array1<f64>> {
834 let mut new_state = state.clone();
835 let cos_half = (angle / 2.0).cos();
836 let sin_half = (angle / 2.0).sin();
837
838 let qubit_mask = 1 << qubit;
839
840 for i in 0..state.len() {
841 if i & qubit_mask == 0 {
842 let j = i | qubit_mask;
843 if j < state.len() {
844 let state_0 = state[i];
845 let state_1 = state[j];
846 new_state[i] = cos_half * state_0 - sin_half * state_1;
847 new_state[j] = sin_half * state_0 + cos_half * state_1;
848 }
849 }
850 }
851
852 Ok(new_state)
853 }
854
855 fn apply_rz_gate(&self, state: &Array1<f64>, qubit: usize, angle: f64) -> Result<Array1<f64>> {
857 let mut new_state = state.clone();
858 let phase_0 = (-angle / 2.0); let phase_1 = (angle / 2.0);
860
861 let qubit_mask = 1 << qubit;
862
863 for i in 0..state.len() {
864 if i & qubit_mask == 0 {
865 new_state[i] *= phase_0.cos(); } else {
867 new_state[i] *= phase_1.cos();
868 }
869 }
870
871 Ok(new_state)
872 }
873
874 fn apply_cnot_gate(
876 &self,
877 state: &Array1<f64>,
878 control: usize,
879 target: usize,
880 ) -> Result<Array1<f64>> {
881 let mut new_state = state.clone();
882 let control_mask = 1 << control;
883 let target_mask = 1 << target;
884
885 for i in 0..state.len() {
886 if i & control_mask != 0 {
887 let j = i ^ target_mask;
888 new_state[i] = state[j];
889 }
890 }
891
892 Ok(new_state)
893 }
894
895 fn apply_cz_gate(
897 &self,
898 state: &Array1<f64>,
899 control: usize,
900 target: usize,
901 ) -> Result<Array1<f64>> {
902 let mut new_state = state.clone();
903 let control_mask = 1 << control;
904 let target_mask = 1 << target;
905
906 for i in 0..state.len() {
907 if (i & control_mask != 0) && (i & target_mask != 0) {
908 new_state[i] *= -1.0; }
910 }
911
912 Ok(new_state)
913 }
914
915 fn decode_output(&self, quantum_state: &Array1<f64>) -> Result<f64> {
917 let mut expectation = 0.0;
919
920 for (i, &litude) in quantum_state.iter().enumerate() {
921 if i & 1 == 0 {
922 expectation += amplitude * amplitude;
923 } else {
924 expectation -= amplitude * amplitude;
925 }
926 }
927
928 Ok(expectation)
929 }
930
931 pub fn compute_derivatives(&self, points: &Array2<f64>) -> Result<DerivativeResults> {
933 let h = 1e-5; let num_points = points.nrows();
935 let num_dims = points.ncols();
936
937 let mut first_derivatives = Array2::zeros((num_points, num_dims));
938 let mut second_derivatives = Array2::zeros((num_points, num_dims));
939 let mut mixed_derivatives = Array3::zeros((num_points, num_dims, num_dims));
940
941 for i in 0..num_points {
942 for j in 0..num_dims {
943 let mut point_plus = points.row(i).to_owned();
945 let mut point_minus = points.row(i).to_owned();
946 point_plus[j] += h;
947 point_minus[j] -= h;
948
949 let output_plus =
950 self.forward(&point_plus.insert_axis(scirs2_core::ndarray::Axis(0)))?[[0, 0]];
951 let output_minus =
952 self.forward(&point_minus.insert_axis(scirs2_core::ndarray::Axis(0)))?[[0, 0]];
953
954 first_derivatives[[i, j]] = (output_plus - output_minus) / (2.0 * h);
955
956 let output_center = self.forward(
958 &points
959 .row(i)
960 .insert_axis(scirs2_core::ndarray::Axis(0))
961 .to_owned(),
962 )?[[0, 0]];
963 second_derivatives[[i, j]] =
964 (output_plus - 2.0 * output_center + output_minus) / (h * h);
965
966 for k in j + 1..num_dims {
968 let mut point_pp = points.row(i).to_owned();
969 let mut point_pm = points.row(i).to_owned();
970 let mut point_mp = points.row(i).to_owned();
971 let mut point_mm = points.row(i).to_owned();
972
973 point_pp[j] += h;
974 point_pp[k] += h;
975 point_pm[j] += h;
976 point_pm[k] -= h;
977 point_mp[j] -= h;
978 point_mp[k] += h;
979 point_mm[j] -= h;
980 point_mm[k] -= h;
981
982 let output_pp =
983 self.forward(&point_pp.insert_axis(scirs2_core::ndarray::Axis(0)))?[[0, 0]];
984 let output_pm =
985 self.forward(&point_pm.insert_axis(scirs2_core::ndarray::Axis(0)))?[[0, 0]];
986 let output_mp =
987 self.forward(&point_mp.insert_axis(scirs2_core::ndarray::Axis(0)))?[[0, 0]];
988 let output_mm =
989 self.forward(&point_mm.insert_axis(scirs2_core::ndarray::Axis(0)))?[[0, 0]];
990
991 let mixed_deriv =
992 (output_pp - output_pm - output_mp + output_mm) / (4.0 * h * h);
993 mixed_derivatives[[i, j, k]] = mixed_deriv;
994 mixed_derivatives[[i, k, j]] = mixed_deriv; }
996 }
997 }
998
999 Ok(DerivativeResults {
1000 first_derivatives,
1001 second_derivatives,
1002 mixed_derivatives,
1003 })
1004 }
1005
1006 pub fn train(&mut self, epochs: Option<usize>) -> Result<()> {
1008 let num_epochs = epochs.unwrap_or(self.config.training_config.epochs);
1009
1010 for epoch in 0..num_epochs {
1011 if self.config.training_config.adaptive_sampling && epoch % 100 == 0 {
1013 self.collocation_points =
1014 Self::generate_adaptive_collocation_points(&self.config, epoch)?;
1015 }
1016
1017 let total_loss = self.compute_total_loss()?;
1019
1020 let gradients = self.compute_gradients()?;
1022
1023 self.update_parameters(&gradients)?;
1025
1026 let metrics = self.compute_training_metrics(epoch, total_loss)?;
1028 self.training_history.push(metrics);
1029
1030 if epoch % 100 == 0 {
1031 println!(
1032 "Epoch {}: Total Loss = {:.6}, PDE Loss = {:.6}, Boundary Loss = {:.6}",
1033 epoch,
1034 self.training_history.last().unwrap().total_loss,
1035 self.training_history.last().unwrap().pde_loss,
1036 self.training_history.last().unwrap().boundary_loss
1037 );
1038 }
1039 }
1040
1041 Ok(())
1042 }
1043
1044 fn generate_adaptive_collocation_points(
1046 config: &QPINNConfig,
1047 epoch: usize,
1048 ) -> Result<Array2<f64>> {
1049 Self::generate_collocation_points(config)
1051 }
1052
1053 fn compute_total_loss(&self) -> Result<TotalLoss> {
1055 let pde_loss = self.compute_pde_loss()?;
1056 let boundary_loss = self.compute_boundary_loss()?;
1057 let initial_loss = self.compute_initial_loss()?;
1058 let physics_constraint_loss = self.compute_physics_constraint_loss()?;
1059
1060 let weights = &self.config.loss_weights;
1061 let total = weights.pde_loss_weight * pde_loss
1062 + weights.boundary_loss_weight * boundary_loss
1063 + weights.initial_loss_weight * initial_loss
1064 + weights.physics_constraint_weight * physics_constraint_loss;
1065
1066 Ok(TotalLoss {
1067 total,
1068 pde_loss,
1069 boundary_loss,
1070 initial_loss,
1071 physics_constraint_loss,
1072 })
1073 }
1074
1075 fn compute_pde_loss(&self) -> Result<f64> {
1077 let derivatives = self.compute_derivatives(&self.collocation_points)?;
1078 let residuals = self.physics_evaluator.compute_pde_residual(
1079 &self.collocation_points,
1080 &self.forward(&self.collocation_points)?,
1081 &derivatives,
1082 )?;
1083
1084 Ok(residuals.iter().map(|r| r * r).sum::<f64>() / residuals.len() as f64)
1085 }
1086
1087 fn compute_boundary_loss(&self) -> Result<f64> {
1089 let boundary_points = self.generate_boundary_points()?;
1091 let boundary_values = self.forward(&boundary_points)?;
1092
1093 let mut total_loss = 0.0;
1094 for (bc, points) in self
1095 .config
1096 .boundary_conditions
1097 .iter()
1098 .zip(boundary_values.rows())
1099 {
1100 let target_values = self.evaluate_boundary_condition(bc, &boundary_points)?;
1101 for (predicted, target) in points.iter().zip(target_values.iter()) {
1102 total_loss += (predicted - target).powi(2);
1103 }
1104 }
1105
1106 Ok(total_loss)
1107 }
1108
1109 fn compute_initial_loss(&self) -> Result<f64> {
1111 let initial_points = self.generate_initial_points()?;
1113 let initial_values = self.forward(&initial_points)?;
1114
1115 let mut total_loss = 0.0;
1116 for (ic, points) in self
1117 .config
1118 .initial_conditions
1119 .iter()
1120 .zip(initial_values.rows())
1121 {
1122 let target_values = self.evaluate_initial_condition(ic, &initial_points)?;
1123 for (predicted, target) in points.iter().zip(target_values.iter()) {
1124 total_loss += (predicted - target).powi(2);
1125 }
1126 }
1127
1128 Ok(total_loss)
1129 }
1130
1131 fn compute_physics_constraint_loss(&self) -> Result<f64> {
1133 let mut constraint_loss = 0.0;
1135
1136 for conservation_law in &self.config.physics_constraints.conservation_laws {
1137 constraint_loss += self.evaluate_conservation_law(conservation_law)?;
1138 }
1139
1140 for symmetry in &self.config.physics_constraints.symmetries {
1141 constraint_loss += self.evaluate_symmetry_constraint(symmetry)?;
1142 }
1143
1144 Ok(constraint_loss)
1145 }
1146
1147 fn generate_boundary_points(&self) -> Result<Array2<f64>> {
1149 let num_boundary_points = 100;
1151 let num_dims = self.config.domain_bounds.len() + 1;
1152 let mut boundary_points = Array2::zeros((num_boundary_points, num_dims));
1153
1154 for i in 0..num_boundary_points {
1156 for (j, &(min_val, max_val)) in self.config.domain_bounds.iter().enumerate() {
1157 if i % 2 == 0 {
1158 boundary_points[[i, j]] = min_val; } else {
1160 boundary_points[[i, j]] = max_val; }
1162 }
1163
1164 let (t_min, t_max) = self.config.time_bounds;
1166 boundary_points[[i, self.config.domain_bounds.len()]] =
1167 t_min + fastrand::f64() * (t_max - t_min);
1168 }
1169
1170 Ok(boundary_points)
1171 }
1172
1173 fn generate_initial_points(&self) -> Result<Array2<f64>> {
1175 let num_initial_points = 100;
1176 let num_dims = self.config.domain_bounds.len() + 1;
1177 let mut initial_points = Array2::zeros((num_initial_points, num_dims));
1178
1179 for i in 0..num_initial_points {
1180 for (j, &(min_val, max_val)) in self.config.domain_bounds.iter().enumerate() {
1182 initial_points[[i, j]] = min_val + fastrand::f64() * (max_val - min_val);
1183 }
1184
1185 initial_points[[i, self.config.domain_bounds.len()]] = self.config.time_bounds.0;
1187 }
1188
1189 Ok(initial_points)
1190 }
1191
1192 fn evaluate_boundary_condition(
1194 &self,
1195 _bc: &BoundaryCondition,
1196 _points: &Array2<f64>,
1197 ) -> Result<Array1<f64>> {
1198 Ok(Array1::zeros(_points.nrows()))
1200 }
1201
1202 fn evaluate_initial_condition(
1204 &self,
1205 _ic: &InitialCondition,
1206 _points: &Array2<f64>,
1207 ) -> Result<Array1<f64>> {
1208 Ok(Array1::zeros(_points.nrows()))
1210 }
1211
1212 fn evaluate_conservation_law(&self, _law: &ConservationLaw) -> Result<f64> {
1214 Ok(0.0)
1216 }
1217
1218 fn evaluate_symmetry_constraint(&self, _symmetry: &Symmetry) -> Result<f64> {
1220 Ok(0.0)
1222 }
1223
1224 fn compute_gradients(&self) -> Result<Array1<f64>> {
1226 let total_loss = self.compute_total_loss()?;
1227 let mut gradients = Array1::zeros(self.parameters.len());
1228 let epsilon = 1e-6;
1229
1230 for i in 0..self.parameters.len() {
1231 let mut params_plus = self.parameters.clone();
1232 params_plus[i] += epsilon;
1233
1234 let mut temp_pinn = self.clone();
1235 temp_pinn.parameters = params_plus;
1236 let loss_plus = temp_pinn.compute_total_loss()?.total;
1237
1238 gradients[i] = (loss_plus - total_loss.total) / epsilon;
1239 }
1240
1241 Ok(gradients)
1242 }
1243
1244 fn update_parameters(&mut self, gradients: &Array1<f64>) -> Result<()> {
1246 let learning_rate = self.config.training_config.learning_rate;
1247
1248 for i in 0..self.parameters.len() {
1249 self.parameters[i] -= learning_rate * gradients[i];
1250 }
1251
1252 Ok(())
1253 }
1254
1255 fn compute_training_metrics(
1257 &self,
1258 epoch: usize,
1259 total_loss: TotalLoss,
1260 ) -> Result<TrainingMetrics> {
1261 Ok(TrainingMetrics {
1262 epoch,
1263 total_loss: total_loss.total,
1264 pde_loss: total_loss.pde_loss,
1265 boundary_loss: total_loss.boundary_loss,
1266 initial_loss: total_loss.initial_loss,
1267 physics_constraint_loss: total_loss.physics_constraint_loss,
1268 quantum_fidelity: 0.9, solution_energy: 1.0, })
1271 }
1272
1273 pub fn get_training_history(&self) -> &[TrainingMetrics] {
1275 &self.training_history
1276 }
1277
1278 pub fn solve_on_grid(&self, grid_points: &Array2<f64>) -> Result<Array1<f64>> {
1280 let solutions = self.forward(grid_points)?;
1281 Ok(solutions.column(0).to_owned())
1282 }
1283}
1284
1285#[derive(Debug)]
1287pub struct DerivativeResults {
1288 pub first_derivatives: Array2<f64>,
1289 pub second_derivatives: Array2<f64>,
1290 pub mixed_derivatives: Array3<f64>,
1291}
1292
1293#[derive(Debug)]
1295pub struct TotalLoss {
1296 pub total: f64,
1297 pub pde_loss: f64,
1298 pub boundary_loss: f64,
1299 pub initial_loss: f64,
1300 pub physics_constraint_loss: f64,
1301}
1302
1303impl PhysicsEvaluator {
1304 pub fn new(equation_type: &PhysicsEquationType) -> Result<Self> {
1306 let mut differential_operators = HashMap::new();
1307
1308 match equation_type {
1309 PhysicsEquationType::Poisson => {
1310 differential_operators.insert(
1311 "laplacian".to_string(),
1312 DifferentialOperator {
1313 operator_type: OperatorType::Laplacian,
1314 order: 2,
1315 direction: vec![0, 1], },
1317 );
1318 }
1319 PhysicsEquationType::Heat => {
1320 differential_operators.insert(
1321 "time_derivative".to_string(),
1322 DifferentialOperator {
1323 operator_type: OperatorType::TimeDerivative,
1324 order: 1,
1325 direction: vec![2], },
1327 );
1328 differential_operators.insert(
1329 "laplacian".to_string(),
1330 DifferentialOperator {
1331 operator_type: OperatorType::Laplacian,
1332 order: 2,
1333 direction: vec![0, 1],
1334 },
1335 );
1336 }
1337 PhysicsEquationType::Wave => {
1338 differential_operators.insert(
1339 "second_time_derivative".to_string(),
1340 DifferentialOperator {
1341 operator_type: OperatorType::TimeDerivative,
1342 order: 2,
1343 direction: vec![2],
1344 },
1345 );
1346 differential_operators.insert(
1347 "laplacian".to_string(),
1348 DifferentialOperator {
1349 operator_type: OperatorType::Laplacian,
1350 order: 2,
1351 direction: vec![0, 1],
1352 },
1353 );
1354 }
1355 _ => {
1356 }
1358 }
1359
1360 Ok(Self {
1361 equation_type: equation_type.clone(),
1362 differential_operators,
1363 })
1364 }
1365
1366 pub fn compute_pde_residual(
1368 &self,
1369 points: &Array2<f64>,
1370 solution: &Array2<f64>,
1371 derivatives: &DerivativeResults,
1372 ) -> Result<Array1<f64>> {
1373 let num_points = points.nrows();
1374 let mut residuals = Array1::zeros(num_points);
1375
1376 match self.equation_type {
1377 PhysicsEquationType::Poisson => {
1378 for i in 0..num_points {
1380 let laplacian = derivatives.second_derivatives[[i, 0]]
1381 + derivatives.second_derivatives[[i, 1]];
1382 residuals[i] = laplacian; }
1384 }
1385 PhysicsEquationType::Heat => {
1386 for i in 0..num_points {
1388 let time_deriv = derivatives.first_derivatives[[i, 2]]; let laplacian = derivatives.second_derivatives[[i, 0]]
1390 + derivatives.second_derivatives[[i, 1]];
1391 residuals[i] = time_deriv - laplacian;
1392 }
1393 }
1394 PhysicsEquationType::Wave => {
1395 for i in 0..num_points {
1397 let second_time_deriv = derivatives.second_derivatives[[i, 2]];
1398 let laplacian = derivatives.second_derivatives[[i, 0]]
1399 + derivatives.second_derivatives[[i, 1]];
1400 residuals[i] = second_time_deriv - laplacian;
1401 }
1402 }
1403 _ => {
1404 return Err(crate::error::MLError::InvalidConfiguration(
1405 "PDE type not implemented".to_string(),
1406 ));
1407 }
1408 }
1409
1410 Ok(residuals)
1411 }
1412}
1413
1414#[cfg(test)]
1415mod tests {
1416 use super::*;
1417
1418 #[test]
1419 fn test_qpinn_creation() {
1420 let config = QPINNConfig::default();
1421 let qpinn = QuantumPINN::new(config);
1422 assert!(qpinn.is_ok());
1423 }
1424
1425 #[test]
1426 fn test_forward_pass() {
1427 let config = QPINNConfig::default();
1428 let qpinn = QuantumPINN::new(config).unwrap();
1429 let input_points = Array2::from_shape_vec(
1430 (5, 3),
1431 vec![
1432 0.1, 0.2, 0.0, 0.3, 0.4, 0.1, 0.5, 0.6, 0.2, 0.7, 0.8, 0.3, 0.9, 1.0, 0.4,
1433 ],
1434 )
1435 .unwrap();
1436
1437 let result = qpinn.forward(&input_points);
1438 assert!(result.is_ok());
1439 assert_eq!(result.unwrap().shape(), [5, 1]);
1440 }
1441
1442 #[test]
1443 fn test_derivative_computation() {
1444 let config = QPINNConfig::default();
1445 let qpinn = QuantumPINN::new(config).unwrap();
1446 let points =
1447 Array2::from_shape_vec((3, 3), vec![0.1, 0.2, 0.0, 0.3, 0.4, 0.1, 0.5, 0.6, 0.2])
1448 .unwrap();
1449
1450 let result = qpinn.compute_derivatives(&points);
1451 assert!(result.is_ok());
1452 }
1453
1454 #[test]
1455 #[ignore]
1456 fn test_training() {
1457 let mut config = QPINNConfig::default();
1458 config.training_config.epochs = 5;
1459 config.training_config.num_collocation_points = 10;
1460
1461 let mut qpinn = QuantumPINN::new(config).unwrap();
1462 let result = qpinn.train(Some(5));
1463 assert!(result.is_ok());
1464 assert!(!qpinn.get_training_history().is_empty());
1465 }
1466}