use computegraph::types::{LocalValueId, OperationRole, ValueRef};
use tenferro_core_ops::PrimitiveOpKind;
use tenferro_tensor::DType;
use tidu::ADRuleResult;
use crate::ad::context::ShapeGuardContext;
use crate::ad::PrimitiveRuleBuilder;
use crate::std_tensor_op::StdTensorOp;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AdRuleSupport {
Supported,
SupportedViaLinearize,
NonDifferentiable,
Unsupported,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct PrimitiveAdSupport {
pub kind: PrimitiveOpKind,
pub linearize: AdRuleSupport,
pub transpose: AdRuleSupport,
}
macro_rules! direct_support {
($kind:ident) => {
PrimitiveAdSupport {
kind: PrimitiveOpKind::$kind,
linearize: AdRuleSupport::Supported,
transpose: AdRuleSupport::Supported,
}
};
}
macro_rules! nondiff_support {
($kind:ident) => {
PrimitiveAdSupport {
kind: PrimitiveOpKind::$kind,
linearize: AdRuleSupport::NonDifferentiable,
transpose: AdRuleSupport::NonDifferentiable,
}
};
}
pub static PRIMITIVE_AD_SUPPORT: [PrimitiveAdSupport; PrimitiveOpKind::COUNT] = [
direct_support!(Add),
direct_support!(Mul),
direct_support!(Neg),
direct_support!(Conj),
direct_support!(Div),
direct_support!(Abs),
direct_support!(Sign),
direct_support!(Maximum),
direct_support!(Minimum),
nondiff_support!(Compare),
direct_support!(Select),
direct_support!(Clamp),
direct_support!(Exp),
direct_support!(Log),
direct_support!(Sin),
direct_support!(Cos),
direct_support!(Tanh),
direct_support!(Sqrt),
direct_support!(Rsqrt),
direct_support!(Pow),
direct_support!(Expm1),
direct_support!(Log1p),
direct_support!(DotGeneral),
direct_support!(ReduceSum),
direct_support!(ReduceProd),
direct_support!(ReduceMax),
direct_support!(ReduceMin),
direct_support!(Transpose),
direct_support!(Reshape),
direct_support!(BroadcastInDim),
direct_support!(Convert),
direct_support!(ExtractDiag),
direct_support!(EmbedDiag),
direct_support!(Tril),
direct_support!(Triu),
direct_support!(Gather),
direct_support!(GatherDynamicSliceSizes),
direct_support!(Scatter),
direct_support!(Slice),
direct_support!(DynamicSlice),
direct_support!(DynamicUpdateSlice),
direct_support!(Pad),
direct_support!(Concatenate),
direct_support!(Reverse),
nondiff_support!(ShapeOf),
direct_support!(DynamicTruncate),
direct_support!(PadToMatch),
nondiff_support!(Constant),
];
pub fn all_primitive_ad_support() -> &'static [PrimitiveAdSupport; PrimitiveOpKind::COUNT] {
&PRIMITIVE_AD_SUPPORT
}
pub fn primitive_ad_support(kind: PrimitiveOpKind) -> &'static PrimitiveAdSupport {
&PRIMITIVE_AD_SUPPORT[kind.as_index()]
}
pub fn is_real_dtype(dtype: DType) -> bool {
matches!(dtype, DType::F32 | DType::F64)
}
pub(crate) fn is_differentiable_dtype(dtype: DType) -> bool {
matches!(dtype, DType::F32 | DType::F64 | DType::C32 | DType::C64)
}
pub(crate) fn linear_transpose_input_active(mode: &OperationRole, input_index: usize) -> bool {
match mode {
OperationRole::Primary => true,
OperationRole::Linearized { active_mask } => {
active_mask.get(input_index).copied().unwrap_or(false)
}
}
}
fn is_complex_dtype(dtype: DType) -> bool {
matches!(dtype, DType::C32 | DType::C64)
}
pub(crate) fn promote_dtype(lhs: DType, rhs: DType) -> DType {
tenferro_tensor::validate::promote_dtype(lhs, rhs)
}
pub(crate) fn promote_dtype_div_like(lhs: DType, rhs: DType) -> DType {
if matches!(lhs, DType::I32 | DType::I64) && matches!(rhs, DType::I32 | DType::I64) {
return DType::F64;
}
promote_dtype(lhs, rhs)
}
pub(crate) fn dtype_of_or_real(ctx: &mut ShapeGuardContext, val: &ValueRef<StdTensorOp>) -> DType {
ctx.metadata_if_available(val)
.map(|metadata| metadata.dtype)
.unwrap_or(DType::F64)
}
#[doc(hidden)]
pub fn conjugate_primal_if_complex(
builder: &mut dyn PrimitiveRuleBuilder,
input: ValueRef<StdTensorOp>,
ctx: &mut ShapeGuardContext,
) -> ADRuleResult<ValueRef<StdTensorOp>> {
let dtype = ctx.dtype_of(&input)?;
Ok(conjugate_primal_if_dtype_complex(builder, input, dtype))
}
#[doc(hidden)]
pub fn conjugate_primal_if_dtype_complex(
builder: &mut dyn PrimitiveRuleBuilder,
input: ValueRef<StdTensorOp>,
dtype: DType,
) -> ValueRef<StdTensorOp> {
if is_complex_dtype(dtype) {
ValueRef::Local(
builder.add_operation(StdTensorOp::Conj, vec![input], OperationRole::Primary)[0],
)
} else {
input
}
}
pub(crate) fn conjugate_primal_if_any_dtype_complex(
builder: &mut dyn PrimitiveRuleBuilder,
input: ValueRef<StdTensorOp>,
dtypes: &[DType],
) -> ValueRef<StdTensorOp> {
if dtypes.iter().copied().any(is_complex_dtype) {
ValueRef::Local(
builder.add_operation(StdTensorOp::Conj, vec![input], OperationRole::Primary)[0],
)
} else {
input
}
}
#[doc(hidden)]
pub fn conjugate_linear_if_dtype_complex(
builder: &mut dyn PrimitiveRuleBuilder,
input: LocalValueId,
dtype: DType,
) -> LocalValueId {
if is_complex_dtype(dtype) {
builder.add_operation(
StdTensorOp::Conj,
vec![ValueRef::Local(input)],
OperationRole::Linearized {
active_mask: vec![true],
},
)[0]
} else {
input
}
}
pub(crate) fn project_linear_to_dtype(
builder: &mut dyn PrimitiveRuleBuilder,
input: LocalValueId,
from: DType,
to: DType,
) -> LocalValueId {
if !is_real_dtype(to) || from == to {
return input;
}
convert_linear_to_dtype(builder, input, from, to)
}
pub(crate) fn convert_linear_to_dtype(
builder: &mut dyn PrimitiveRuleBuilder,
input: LocalValueId,
from: DType,
to: DType,
) -> LocalValueId {
if from == to {
return input;
}
builder.add_operation(
StdTensorOp::Convert { from, to },
vec![ValueRef::Local(input)],
OperationRole::Linearized {
active_mask: vec![true],
},
)[0]
}
pub(crate) fn convert_fixed_ref_to_dtype(
builder: &mut dyn PrimitiveRuleBuilder,
input: ValueRef<StdTensorOp>,
from: DType,
to: DType,
) -> ValueRef<StdTensorOp> {
if from == to {
return input;
}
ValueRef::Local(
builder.add_operation(
StdTensorOp::Convert { from, to },
vec![input],
OperationRole::Linearized {
active_mask: vec![false],
},
)[0],
)
}
#[cfg(test)]
mod tests;