use candle_core::{Result, Tensor};
use candle_nn::ops;
use candle_nn::VarBuilder;
pub type EssLlikFn<'a> = Box<dyn Fn(&Tensor) -> Result<Tensor> + 'a>;
pub trait EncoderModuleT {
fn forward_t(
&self,
x_nd: &Tensor,
x0_nd: Option<&Tensor>,
train: bool,
) -> Result<(Tensor, Tensor)>;
fn dim_latent(&self) -> usize;
}
pub trait JointEncoderModuleT {
fn forward_t(
&self,
x_nd_vec: &[Tensor],
x0_nd_vec: &[Option<Tensor>],
train: bool,
) -> Result<(Tensor, Tensor)>;
fn dim_latent(&self) -> usize;
}
pub trait DecoderModuleT {
fn forward(&self, z_nk: &Tensor) -> Result<Tensor>;
fn get_dictionary(&self) -> Result<Tensor>;
fn forward_with_llik<LlikFn>(
&self,
z_nk: &Tensor,
x_nd: &Tensor,
llik: &LlikFn,
) -> Result<(Tensor, Tensor)>
where
LlikFn: Fn(&Tensor, &Tensor) -> Result<Tensor>;
fn llik_is_gene_chunked(&self) -> bool {
false
}
fn llik_gene_chunked(&self, z_nk: &Tensor, x_nd: &Tensor, gene_chunk: usize) -> Result<Tensor> {
let _ = gene_chunk;
let dense = |_: &Tensor, _: &Tensor| -> Result<Tensor> {
candle_core::bail!("the dense fallback needs no likelihood closure")
};
let (_, llik) = self.forward_with_llik(z_nk, x_nd, &dense)?;
Ok(llik)
}
fn dim_obs(&self) -> usize;
fn dim_latent(&self) -> usize;
fn attach_feature_weights(
&mut self,
_weights: &[f32],
_dev: &candle_core::Device,
) -> Result<()> {
Ok(())
}
fn build_ess_llik<'a>(
&'a self,
x_nd: &'a Tensor,
topic_smoothing: f64,
) -> Result<EssLlikFn<'a>> {
let log_dict_dk = self.get_dictionary()?.detach();
let beta_kd = log_dict_dk.t()?.exp()?.contiguous()?;
let x_pos = x_nd.clamp(0.0, f64::INFINITY)?;
let k = self.dim_latent() as f64;
Ok(Box::new(move |z_nk: &Tensor| {
let mut z = ops::softmax(z_nk, 1)?;
if topic_smoothing > 0.0 {
z = ((z * (1.0 - topic_smoothing))? + topic_smoothing / k)?;
}
let recon = z.matmul(&beta_kd)?;
x_pos.mul(&(recon + 1e-8)?.log()?)?.sum(x_pos.rank() - 1)
}))
}
}
pub trait NewDecoder: Sized {
fn new(n_features: usize, n_topics: usize, vs: VarBuilder) -> Result<Self>;
}
pub fn joint_multinomial_llik(
log_recon_vec: Vec<Tensor>,
x_nd_vec: &[Tensor],
) -> Result<(Vec<Tensor>, Tensor)> {
let recon_vec: Vec<Tensor> = log_recon_vec
.iter()
.map(|x| x.exp())
.collect::<Result<Vec<_>>>()?;
let llik_vec = x_nd_vec
.iter()
.zip(&log_recon_vec)
.map(|(x, log_recon)| -> Result<Tensor> {
let ret = x
.clamp(0.0, f64::INFINITY)?
.mul(log_recon)?
.sum(x.rank() - 1)?;
ret.unsqueeze(ret.rank())
})
.collect::<Result<Vec<Tensor>>>()?;
let k = llik_vec[0].rank();
let llik = Tensor::cat(&llik_vec, k - 1)?.sum(k - 1)?;
Ok((recon_vec, llik))
}
pub trait JointDecoderModuleT {
fn forward(&self, z_nk: &Tensor) -> Result<Vec<Tensor>>;
fn get_dictionary(&self) -> Result<Vec<Tensor>>;
fn forward_with_llik<LlikFn>(
&self,
z_nk: &Tensor,
x_nd: &[Tensor],
llik: &LlikFn,
) -> Result<(Vec<Tensor>, Tensor)>
where
LlikFn: Fn(&Tensor, &Tensor) -> Result<Tensor>;
fn dim_obs(&self) -> &[usize];
fn dim_latent(&self) -> usize;
}
pub struct MatchedEncoderData<'a> {
pub left: &'a Tensor,
pub right: &'a Tensor,
pub aux_left: Option<&'a Tensor>,
pub aux_right: Option<&'a Tensor>,
}
pub struct MatchedDecoderData<'a> {
pub left: &'a Tensor,
pub right: &'a Tensor,
pub delta_left: Option<&'a Tensor>,
pub delta_right: Option<&'a Tensor>,
}
pub trait MatchedEncoderModuleT {
fn forward_t(&self, data: MatchedEncoderData, train: bool) -> Result<MatchedEncoderLatent>;
fn dim_obs(&self) -> usize;
fn dim_latent(&self) -> usize;
}
pub struct MatchedEncoderLatent {
pub logits_theta_left: Tensor,
pub logits_theta_right: Tensor,
pub kl_div: Tensor,
}
pub struct MatchedDecoderRecon {
pub x_left: Tensor,
pub x_right: Tensor,
}
pub trait MatchedDecoderModuleT {
fn forward(&self, latent: &MatchedEncoderLatent) -> Result<MatchedDecoderRecon>;
fn get_dictionary(&self) -> Result<Tensor>;
fn forward_with_llik<LlikFn>(
&self,
latent: &MatchedEncoderLatent,
x_pair: MatchedDecoderData,
llik: &LlikFn,
) -> Result<(MatchedDecoderRecon, Tensor)>
where
LlikFn: Fn(&Tensor, &Tensor) -> Result<Tensor>;
fn dim_obs(&self) -> usize;
fn dim_latent(&self) -> usize;
}