ferritin_plms/esm3/utils/
affine3d.rs1use candle_core::{D, Result, Tensor};
7
8pub struct Affine3D {
13 pub rot: Tensor,
14 pub trans: Tensor,
15}
16
17impl Affine3D {
18 pub fn new(rot: Tensor, trans: Tensor) -> Self {
19 Self { rot, trans }
20 }
21
22 pub fn apply_rot(rot: &Tensor, v: &Tensor) -> Result<Tensor> {
28 let rot_t = rot.transpose(D::Minus2, D::Minus1)?;
29 v.matmul(&rot_t)
30 }
31
32 pub fn apply_rot_inv(rot: &Tensor, v: &Tensor) -> Result<Tensor> {
38 v.matmul(rot)
39 }
40
41 pub fn apply(&self, v: &Tensor) -> Result<Tensor> {
45 let rotated = Self::apply_rot(&self.rot, v)?; let trans = self.trans.unsqueeze(D::Minus2)?; rotated.broadcast_add(&trans)
48 }
49
50 pub fn build_affine3d_from_coordinates(coords: &Tensor) -> Result<(Self, Tensor)> {
63 let n_pos = coords.narrow(D::Minus2, 0, 1)?.squeeze(D::Minus2)?;
65 let ca_pos = coords.narrow(D::Minus2, 1, 1)?.squeeze(D::Minus2)?;
66 let c_pos = coords.narrow(D::Minus2, 2, 1)?.squeeze(D::Minus2)?;
67
68 let x_axis = ca_pos.sub(&c_pos)?; let xy_plane = n_pos.sub(&ca_pos)?; let rot = graham_schmidt(&x_axis, &xy_plane, 1e-10)?; let eps = 1e-8f64;
77 let v1_norm_sq = x_axis.sqr()?.sum(D::Minus1)?; let v2_norm_sq = xy_plane.sqr()?.sum(D::Minus1)?;
79 let mask = (v1_norm_sq.gt(eps)? * v2_norm_sq.gt(eps)?)?;
80
81 Ok((Self::new(rot, ca_pos), mask))
82 }
83}
84
85fn graham_schmidt(x_axis: &Tensor, xy_plane: &Tensor, eps: f64) -> Result<Tensor> {
90 let norm_x = x_axis
92 .sqr()?
93 .sum_keepdim(D::Minus1)?
94 .sqrt()?
95 .affine(1.0, eps)?;
96 let e_x = x_axis.broadcast_div(&norm_x)?;
97
98 let dot = e_x.mul(xy_plane)?.sum_keepdim(D::Minus1)?; let e_1 = xy_plane.sub(&e_x.broadcast_mul(&dot)?)?;
101 let norm_1 = e_1
102 .sqr()?
103 .sum_keepdim(D::Minus1)?
104 .sqrt()?
105 .affine(1.0, eps)?;
106 let e_1 = e_1.broadcast_div(&norm_1)?;
107
108 let e_2 = cross_product(&e_x, &e_1)?;
110
111 Tensor::cat(
113 &[
114 &e_x.unsqueeze(D::Minus1)?,
115 &e_1.unsqueeze(D::Minus1)?,
116 &e_2.unsqueeze(D::Minus1)?,
117 ],
118 D::Minus1,
119 )
120}
121
122fn cross_product(a: &Tensor, b: &Tensor) -> Result<Tensor> {
124 let a0 = a.narrow(D::Minus1, 0, 1)?;
125 let a1 = a.narrow(D::Minus1, 1, 1)?;
126 let a2 = a.narrow(D::Minus1, 2, 1)?;
127 let b0 = b.narrow(D::Minus1, 0, 1)?;
128 let b1 = b.narrow(D::Minus1, 1, 1)?;
129 let b2 = b.narrow(D::Minus1, 2, 1)?;
130 let c0 = a1.mul(&b2)?.sub(&a2.mul(&b1)?)?;
131 let c1 = a2.mul(&b0)?.sub(&a0.mul(&b2)?)?;
132 let c2 = a0.mul(&b1)?.sub(&a1.mul(&b0)?)?;
133 Tensor::cat(&[&c0, &c1, &c2], D::Minus1)
134}
135
136#[cfg(test)]
137mod tests {
138 use super::*;
139 use candle_core::{Device, DType, Tensor};
140
141 #[test]
142 fn test_apply_rot_identity() -> Result<()> {
143 let device = &Device::Cpu;
144 let rot = Tensor::eye(3, candle_core::DType::F32, device)?
146 .unsqueeze(0)?
147 .unsqueeze(0)?; let v = Tensor::randn(0f32, 1f32, (1, 1, 4, 3), device)?;
149 let out = Affine3D::apply_rot(&rot, &v)?;
150 let diff = out.sub(&v)?.sqr()?.sum_all()?.to_scalar::<f32>()?;
151 assert!(diff < 1e-5, "identity rotation changed vectors");
152 Ok(())
153 }
154
155 #[test]
156 fn test_apply_rot_roundtrip() -> Result<()> {
157 let device = &Device::Cpu;
158 let v1 = Tensor::randn(0f32, 1f32, (1, 1, 3), device)?;
160 let v2 = Tensor::randn(0f32, 1f32, (1, 1, 3), device)?;
161 let rot = graham_schmidt(&v1, &v2, 1e-10)?; let x = Tensor::randn(0f32, 1f32, (1, 1, 5, 3), device)?;
164 let x_rot = Affine3D::apply_rot(&rot, &x)?;
166 let x_back = Affine3D::apply_rot_inv(&rot, &x_rot)?;
167 let diff = x_back.sub(&x)?.sqr()?.sum_all()?.to_scalar::<f32>()?;
168 assert!(diff < 1e-5, "rot/rot_inv roundtrip failed: {}", diff);
169 Ok(())
170 }
171
172 #[test]
173 fn test_build_affine3d_from_coordinates() -> Result<()> {
174 let device = &Device::Cpu;
175 let n = Tensor::new(&[[[0f32, 0., 0.], [1., 0., 0.], [2., 0., 0.]]], device)?;
177 let ca = Tensor::new(&[[[1f32, 0., 0.], [2., 0., 0.], [3., 0., 0.]]], device)?;
178 let c = Tensor::new(&[[[2f32, 0., 0.], [3., 0., 0.], [4., 0., 0.]]], device)?;
179 let n_e = n.unsqueeze(2)?;
181 let ca_e = ca.unsqueeze(2)?;
182 let c_e = c.unsqueeze(2)?;
183 let coords = Tensor::cat(&[&n_e, &ca_e, &c_e], 2)?; let (_affine, mask) = Affine3D::build_affine3d_from_coordinates(&coords)?;
186 let mask_sum = mask.to_dtype(DType::F32)?.sum_all()?.to_scalar::<f32>()?;
189 assert!(mask_sum >= 0.0);
191 Ok(())
192 }
193}