use crate::Config;
use crate::clue_errors::CluEError;
use crate::cluster::{Cluster,
get_subclusters::build_subclusters,
find_clusters::ClusterSet};
use crate::signal::{Signal, load_batch_signals, write_batch_signals};
use crate::structure::Structure;
use crate::HamiltonianTensors;
use crate::math;
use crate::quantum::spin_hamiltonian::*;
use rayon::prelude::*;
use std::path::Path;
pub fn do_cluster_correlation_expansion(
cluster_set: &mut ClusterSet, spin_ops: &ClusterSpinOperators,
tensors: &HamiltonianTensors, config: &Config,
save_path_opt: &Option<String>,structure: &Structure,
) -> Result<Vec::<Signal>,CluEError>
{
calculate_auxiliary_signals(cluster_set, spin_ops, tensors, config,
save_path_opt,structure)?;
let n_tot = config.number_timepoints.iter().sum::<usize>();
let max_size = cluster_set.clusters.len();
let mut order_n_signals = Vec::<Signal>::with_capacity(max_size);
let mut signal = Signal::ones(n_tot);
for cluster_of_size in cluster_set.clusters.iter(){
for cluster in cluster_of_size.iter(){
match &cluster.signal{
Ok(Some(aux_sig)) => signal = &signal * aux_sig,
Ok(None) => (),
Err(err) => return Err(err.clone()),
}
}
order_n_signals.push(signal.clone());
}
Ok(order_n_signals)
}
fn calculate_auxiliary_signals(
cluster_set: &mut ClusterSet, spin_ops: &ClusterSpinOperators,
tensors: &HamiltonianTensors, config: &Config,
save_path_opt: &Option<String>, structure: &Structure,
)
-> Result<(),CluEError>
{
let clusters = &mut cluster_set.clusters;
let cluster_indices = &cluster_set.cluster_indices;
let Some(batch_size) = config.cluster_batch_size else{
return Err(CluEError::NoClusterBatchSize);
};
let n_tot = config.number_timepoints.iter().sum::<usize>();
let max_size = clusters.len();
for cluster_size in 1..=max_size{
let n_clusters = clusters[cluster_size-1].len();
let n_batches
= math::ceil( (n_clusters as f64)/(batch_size as f64)) as usize;
for ibatch in 0..n_batches{
let idx = ibatch*batch_size;
if let Some(path) = &save_path_opt{
if let Some(aux_dir) = &config.write_auxiliary_signals{
let load_dir = format!("{}/{}",path, aux_dir);
let aux_filename = format!("{}/cluster_size_{}_batch_{}.csv",
load_dir, cluster_size,ibatch);
if Path::new(&aux_filename).exists(){
load_batch_signals(&mut clusters[cluster_size-1],
idx,batch_size,&aux_filename)?;
continue;
}
}
}
clusters[cluster_size-1].par_iter_mut().skip(idx).take(batch_size)
.for_each(|cluster|
cluster.signal = calculate_cluster_signal(cluster.vertices(),
spin_ops,tensors,config)
);
for iclu in (0..n_clusters).skip(idx).take(batch_size){
let mut cluster = clusters[cluster_size-1][iclu].clone();
let subclusters = build_subclusters(cluster.vertices());
let aux_signal: &mut Signal = match &mut cluster.signal{
Ok(Some(sig)) => sig,
Ok(None) => return Err(
CluEError::ClusterHasNoSignal(cluster.to_string())),
Err(err) => return Err(err.clone()),
};
for subcluster_vertices in subclusters.iter(){
if subcluster_vertices.is_empty(){ continue; }
let subcluster_size = subcluster_vertices.len();
if subcluster_size >= cluster_size{
let subcluster = Cluster::from(subcluster_vertices.clone());
return Err(CluEError::NotAProperSubset(
subcluster.to_string(),cluster.to_string()));
}
if let Some(subcluster_idx) = cluster_indices[subcluster_size-1]
.get(subcluster_vertices)
{
let subcluster = &clusters[subcluster_size-1][*subcluster_idx];
match &subcluster.signal{
Ok(Some(subsignal)) => *aux_signal = &(*aux_signal)/subsignal,
Ok(None) => continue,
Err(err) => return Err(err.clone()),
}
}
}
clusters[cluster_size-1][iclu] = cluster;
}
if let Some(path) = &save_path_opt{
if let Some(aux_dir) = &config.write_auxiliary_signals{
let save_dir = format!("{}/{}",path, aux_dir);
match std::fs::create_dir_all(save_dir.clone()){
Ok(_) => (),
Err(_) => return Err(CluEError::CannotCreateDir(save_dir)),
}
let aux_filename = format!("{}/cluster_size_{}_batch_{}.csv",
save_dir, cluster_size,ibatch);
write_batch_signals(&clusters[cluster_size-1], n_tot, idx, batch_size,
&aux_filename, structure)?;
}
}
}
}
Ok(())
}
fn calculate_cluster_signal(tensor_indices: &[usize],
spin_ops: &ClusterSpinOperators, tensors: &HamiltonianTensors,
config: &Config)
-> Result<Option<Signal>,CluEError>
{
let hamiltonian = build_hamiltonian(tensor_indices,spin_ops, tensors,config)?;
let density_matrix = get_density_matrix(&hamiltonian, config)?;
let signal = propagate_pulse_sequence(&density_matrix, &hamiltonian, config)?;
Ok(Some(signal))
}
#[cfg(test)]
mod tests{
use super::*;
use crate::cluster::adjacency::AdjacencyList;
use crate::config::PulseSequence;
use crate::find_clusters;
use crate::physical_constants::ONE;
use crate::quantum::tensors::*;
use crate::signal::calculate_analytic_restricted_2cluster_signals::*;
use crate::structure::particle::Particle;
use crate::space_3d::{SymmetricTensor3D,Vector3D};
#[test]
fn test_do_cluster_correlation_expansion(){
let z0 = 33.0e9;
let z1 = 80.0e6;
let a1 = 10.0e6;
let a2 = -10.0e6;
let b = 10.0e3;
let tensors = build_restricted_three_spin_tensors(z0, z1, a1, a2, b);
let spin_ops = ClusterSpinOperators::new(&vec![2],3).unwrap();
let mut config = Config::new();
config.number_timepoints = vec![21];
let delta_hf = a1 - a2;
let freq = hahn_three_spin_modulation_frequency(delta_hf,b);
config.time_increments = vec![0.05/freq];
config.pulse_sequence = Some(PulseSequence::CarrPurcell(1));
config.set_defaults().unwrap();
config.construct_time_axis().unwrap();
let mut adjacency_list = AdjacencyList::with_capacity(5);
adjacency_list.connect(1,2);
adjacency_list.connect(1,3);
adjacency_list.connect(1,4);
adjacency_list.connect(2,3);
adjacency_list.connect(2,4);
adjacency_list.connect(3,4);
let mut cluster_set = find_clusters(&adjacency_list, 3).unwrap();
let bath_particles = Vec::<Particle>::with_capacity(4);
let connections = AdjacencyList::with_capacity(4);
let cell_offsets = vec![Vector3D::zeros()];
let structure = Structure::new( bath_particles, connections, cell_offsets);
let order_n_signals = do_cluster_correlation_expansion(&mut cluster_set,
&spin_ops, &tensors, &config, &None, &structure).unwrap();
let spin_indices = vec![1,2];
let ref_signal_opt = analytic_restricted_2cluster_signal(
&spin_indices,&tensors,&config).unwrap();
let Some(ref_signal) = ref_signal_opt else{
panic!("Could not calculate reference signal.");
};
for (ii,v) in order_n_signals[1].data.iter().enumerate(){
let v0 = ref_signal.data[ii];
assert!((v-v0*v0).norm() < 1e-12);
assert!((order_n_signals[0].data[ii]-ONE).norm() < 1e-12);
}
}
#[test]
fn test_calculate_cluster_signal(){
let z0 = 33.0e9;
let z1 = 80.0e6;
let a1 = 10.0e6;
let a2 = -10.0e6;
let b = 10.0e3;
let tensors = build_restricted_three_spin_tensors(z0, z1, a1, a2, b);
let spin_indices = vec![1,2];
let spin_ops = ClusterSpinOperators::new(&vec![2],2).unwrap();
let mut config = Config::new();
config.number_timepoints = vec![21];
let delta_hf = a1 - a2;
let freq = hahn_three_spin_modulation_frequency(delta_hf,b);
config.time_increments = vec![0.05/freq];
config.pulse_sequence = Some(PulseSequence::CarrPurcell(1));
config.set_defaults().unwrap();
config.construct_time_axis().unwrap();
let signal_opt = calculate_cluster_signal(&vec![1,2], &spin_ops, &tensors,
&config).unwrap();
let Some(signal) = signal_opt else{
panic!("Could not calculate signal.");
};
assert_eq!(signal.data.len(),21);
let ref_signal_opt = analytic_restricted_2cluster_signal(
&spin_indices,&tensors,&config).unwrap();
let Some(ref_signal) = ref_signal_opt else{
panic!("Could not calculate reference signal.");
};
for (ii,v) in signal.data.iter().enumerate(){
let v0 = ref_signal.data[ii];
let err: f64;
if (v+v0).norm() < 1e12{
err = (v-v0).norm();
}else{
err = (2.0+(v-v0)/(v+v0)).norm();
}
assert!(err < 1e-9);
}
}
fn build_restricted_three_spin_tensors(z0: f64, z1: f64, a1: f64, a2: f64,
b: f64) -> HamiltonianTensors{
let spin_multiplicities = vec![2,2,2,2,2];
let mut spin1_tensors = Spin1Tensors::new(5);
let zeeman0 = Vector3D::from([0.0, 0.0, z0]);
let zeeman1 = Vector3D::from([0.0, 0.0, z1]);
spin1_tensors.set(0,zeeman0);
spin1_tensors.set(1,zeeman1.clone());
spin1_tensors.set(2,zeeman1.clone());
spin1_tensors.set(3,zeeman1.clone());
spin1_tensors.set(4,zeeman1);
let mut spin2_tensors = Spin2Tensors::new(5);
let hf1 = SymmetricTensor3D::from([ 0.0, 0.0, 0.0,
0.0, 0.0,
a1]);
let hf2 = SymmetricTensor3D::from([ 0.0, 0.0, 0.0,
0.0, 0.0,
a2]);
let dip = SymmetricTensor3D::from([ -b/2.0, 0.0, 0.0,
-b/2.0, 0.0,
b]);
spin2_tensors.set(0,1,hf1.clone());
spin2_tensors.set(0,2,hf2.clone());
spin2_tensors.set(1,2,dip.clone());
spin2_tensors.set(0,3,hf1);
spin2_tensors.set(0,4,hf2);
spin2_tensors.set(3,4,dip);
let ge = -1.7609e11;
HamiltonianTensors{
spin_multiplicities,
spin1_tensors,
spin2_tensors,
detected_gamma_matrix: SymmetricTensor3D::from([ge, 0.0, 0.0,
ge, 0.0,
ge]),
magnetic_field: Vector3D::from([0.0,0.0,1.2]),
}
}
}