tenferro_capi/
lib.rs

1//! C-API (FFI) for tenferro.
2//!
3//! Exposes tensor lifecycle, einsum, SVD (including AD rules), and DLPack
4//! interop to host languages such as Julia, Python (JAX, PyTorch), and C/C++.
5//!
6//! # Design principles
7//!
8//! - **Opaque pointers**: `TfeTensorF64` is an opaque handle wrapping
9//!   `Tensor<f64>`. Host languages never see Rust internals.
10//! - **Status codes**: Every function takes a `*mut tfe_status_t` as its
11//!   last argument. Rust panics are caught with `catch_unwind` and
12//!   converted to `TFE_INTERNAL_ERROR`.
13//! - **Stateless AD rules**: Only `rrule` (VJP) and `frule` (JVP) are
14//!   exposed. The AD tape / `TrackedTensor` / `DualTensor` are Rust-internal
15//!   and **not** exposed via FFI. Host languages manage their own AD tapes
16//!   (ChainRules.jl, PyTorch autograd, JAX custom_vjp).
17//! - **f64 only** in this POC phase. All functions carry the `_f64` suffix.
18//! - **DLPack interop**: Zero-copy tensor exchange with Julia, Python, and
19//!   other frameworks via [`DLManagedTensorVersioned`]. Supports CPU and
20//!   GPU memory. Use [`tfe_tensor_f64_to_dlpack`] (export) and
21//!   [`tfe_tensor_f64_from_dlpack`] (import).
22//! - **Copy semantics** for convenience functions: `tfe_tensor_f64_from_data`
23//!   copies the caller's data into a Rust-owned buffer. For zero-copy, use
24//!   DLPack.
25//!
26//! # Memory ownership
27//!
28//! | Allocation | Freed by |
29//! |-----------|----------|
30//! | Tensor from `_from_data` / `_zeros` / `_clone` | `tfe_tensor_f64_release` |
31//! | Tensor from `_from_dlpack` | `tfe_tensor_f64_release` (calls DLPack deleter) |
32//! | Output tensor (via `**_out`) | `tfe_tensor_f64_release` |
33//! | Gradient tensor (rrule output) | `tfe_tensor_f64_release` |
34//! | `grads_out` array (einsum rrule) | Caller provides buffer |
35//! | Input `data` pointer | Caller (data is copied) |
36//! | `DLManagedTensorVersioned` from `_to_dlpack` | Consumer calls `deleter` |
37//!
38//! # Example (C pseudocode)
39//!
40//! ```c
41//! tfe_status_t status;
42//! size_t shape[] = {3, 4};
43//! double data[12] = { /* ... */ };
44//!
45//! tfe_tensor_f64 *a = tfe_tensor_f64_from_data(data, 12, shape, 2, &status);
46//! assert(status == TFE_SUCCESS);
47//!
48//! const tfe_tensor_f64 *ops[] = {a, a};
49//! tfe_tensor_f64 *c = tfe_einsum_f64("ij,jk->ik", ops, 2, &status);
50//!
51//! tfe_tensor_f64_release(c);
52//! tfe_tensor_f64_release(a);
53//! ```
54
55#![allow(clippy::missing_safety_doc)]
56#![allow(non_camel_case_types)]
57
58use std::os::raw::{c_char, c_void};
59
60// ============================================================================
61// Status codes
62// ============================================================================
63
64/// Status code type returned by all C-API functions.
65pub type tfe_status_t = i32;
66
67/// Operation completed successfully.
68pub const TFE_SUCCESS: tfe_status_t = 0;
69
70/// Invalid argument (null pointer, bad subscript string, etc.).
71pub const TFE_INVALID_ARGUMENT: tfe_status_t = -1;
72
73/// Tensor shape mismatch for the requested operation.
74pub const TFE_SHAPE_MISMATCH: tfe_status_t = -2;
75
76/// Internal error (Rust panic or unexpected failure).
77pub const TFE_INTERNAL_ERROR: tfe_status_t = -3;
78
79// ============================================================================
80// DLPack v1.0 C ABI types
81// ============================================================================
82
83/// DLPack version information.
84#[repr(C)]
85pub struct DLPackVersion {
86    /// Major version (1 for DLPack v1.0).
87    pub major: u32,
88    /// Minor version.
89    pub minor: u32,
90}
91
92/// DLPack device descriptor.
93#[repr(C)]
94pub struct DLDevice {
95    /// Device type (see `KDLCPU`, `KDLCUDA`, `KDLCUDA_HOST`, `KDLCUDA_MANAGED`,
96    /// `KDLROCM`, `KDLROCM_HOST`).
97    pub device_type: i32,
98    /// Device ID. 0 for CPU, pinned, and managed memory; GPU ordinal for
99    /// device-local memory.
100    pub device_id: i32,
101}
102
103/// DLPack data type descriptor.
104#[repr(C)]
105pub struct DLDataType {
106    /// Type code (see `kDLFloat`, `kDLInt`, `kDLComplex`).
107    pub code: u8,
108    /// Number of bits per element (e.g., 64 for f64).
109    pub bits: u8,
110    /// Number of lanes (1 for scalar, >1 for SIMD vector types).
111    pub lanes: u16,
112}
113
114/// DLPack tensor descriptor (unmanaged).
115///
116/// Describes the memory layout of a tensor without ownership information.
117/// Used as a field within [`DLManagedTensorVersioned`].
118#[repr(C)]
119pub struct DLTensor {
120    /// Pointer to the data. For GPU tensors, this is a device pointer.
121    pub data: *mut c_void,
122    /// Device where the data resides.
123    pub device: DLDevice,
124    /// Number of dimensions.
125    pub ndim: i32,
126    /// Data type.
127    pub dtype: DLDataType,
128    /// Shape array (length = ndim). Owned by the manager.
129    pub shape: *mut i64,
130    /// Strides array in **element units** (not bytes). NULL = row-major contiguous.
131    pub strides: *mut i64,
132    /// Byte offset from `data` pointer to the first element.
133    pub byte_offset: u64,
134}
135
136/// DLPack managed tensor with version and ownership (DLPack v1.0+).
137///
138/// This is the primary type for DLPack tensor exchange. The `deleter`
139/// callback must be called by the consumer when the data is no longer needed.
140#[repr(C)]
141pub struct DLManagedTensorVersioned {
142    /// DLPack version.
143    pub version: DLPackVersion,
144    /// Opaque pointer for the producer's use (e.g., Box<Tensor>).
145    pub manager_ctx: *mut c_void,
146    /// Callback to free resources. Must be called exactly once by the consumer.
147    pub deleter: Option<unsafe extern "C" fn(*mut DLManagedTensorVersioned)>,
148    /// Bitmask flags (see `DLPACK_FLAG_*` constants).
149    pub flags: u64,
150    /// The tensor descriptor.
151    pub dl_tensor: DLTensor,
152}
153
154// DLDeviceType constants
155/// CPU device.
156pub const KDLCPU: i32 = 1;
157/// NVIDIA CUDA GPU device memory.
158pub const KDLCUDA: i32 = 2;
159/// Pinned CUDA CPU memory (`cudaMallocHost`).
160pub const KDLCUDA_HOST: i32 = 3;
161/// AMD ROCm GPU device memory.
162pub const KDLROCM: i32 = 10;
163/// Pinned ROCm CPU memory (`hipMallocHost`).
164pub const KDLROCM_HOST: i32 = 11;
165/// CUDA managed/unified memory (`cudaMallocManaged`).
166pub const KDLCUDA_MANAGED: i32 = 13;
167
168// DLDataTypeCode constants
169/// Integer type code.
170pub const KDLINT: u8 = 0;
171/// Floating-point type code.
172pub const KDLFLOAT: u8 = 2;
173/// Complex type code.
174pub const KDLCOMPLEX: u8 = 5;
175
176// DLPack flags
177/// Data is read-only (consumer must not write).
178pub const DLPACK_FLAG_BITMASK_READ_ONLY: u64 = 1 << 0;
179/// Data was copied (not zero-copy).
180pub const DLPACK_FLAG_BITMASK_IS_COPIED: u64 = 1 << 1;
181
182// ============================================================================
183// Opaque tensor handle
184// ============================================================================
185
186/// Opaque handle wrapping a `Tensor<f64>`.
187///
188/// Host languages hold a pointer to this type and pass it to all
189/// `tfe_*` functions. The internal layout is private; only the C-API
190/// functions can access the inner tensor.
191///
192/// # Examples (C)
193///
194/// ```c
195/// tfe_status_t status;
196/// size_t shape[] = {2, 3};
197/// double data[] = {1, 2, 3, 4, 5, 6};
198/// tfe_tensor_f64 *t = tfe_tensor_f64_from_data(data, 6, shape, 2, &status);
199/// // ... use t ...
200/// tfe_tensor_f64_release(t);
201/// ```
202#[repr(C)]
203pub struct TfeTensorF64 {
204    _private: [u8; 0],
205}
206
207// ============================================================================
208// Tensor lifecycle
209// ============================================================================
210
211/// Create a tensor from caller-provided data (copy semantics).
212///
213/// The data is **copied** into Rust-owned storage. The caller retains
214/// ownership of the `data` pointer and may free it after this call.
215/// The internal memory layout (strides) is implementation-defined.
216///
217/// For zero-copy tensor exchange with specific memory layouts, use
218/// [`tfe_tensor_f64_from_dlpack`] instead.
219///
220/// # Safety
221///
222/// - `data` must point to at least `len` valid `f64` values.
223/// - `shape` must point to at least `ndim` valid `usize` values.
224/// - `status` must be a valid, non-null pointer.
225///
226/// # Examples (C)
227///
228/// ```c
229/// double data[] = {1.0, 2.0, 3.0, 4.0, 5.0, 6.0};
230/// size_t shape[] = {2, 3};
231/// tfe_status_t status;
232/// tfe_tensor_f64 *t = tfe_tensor_f64_from_data(data, 6, shape, 2, &status);
233/// assert(status == TFE_SUCCESS);
234/// tfe_tensor_f64_release(t);
235/// ```
236#[no_mangle]
237pub unsafe extern "C" fn tfe_tensor_f64_from_data(
238    _data: *const f64,
239    _len: usize,
240    _shape: *const usize,
241    _ndim: usize,
242    _status: *mut tfe_status_t,
243) -> *mut TfeTensorF64 {
244    todo!()
245}
246
247/// Create a tensor filled with zeros.
248///
249/// The internal memory layout is implementation-defined.
250///
251/// # Safety
252///
253/// - `shape` must point to at least `ndim` valid `usize` values.
254/// - `status` must be a valid, non-null pointer.
255///
256/// # Examples (C)
257///
258/// ```c
259/// size_t shape[] = {3, 4};
260/// tfe_status_t status;
261/// tfe_tensor_f64 *t = tfe_tensor_f64_zeros(shape, 2, &status);
262/// tfe_tensor_f64_release(t);
263/// ```
264#[no_mangle]
265pub unsafe extern "C" fn tfe_tensor_f64_zeros(
266    _shape: *const usize,
267    _ndim: usize,
268    _status: *mut tfe_status_t,
269) -> *mut TfeTensorF64 {
270    todo!()
271}
272
273/// Deep-copy a tensor.
274///
275/// # Safety
276///
277/// - `tensor` must be a valid pointer returned by a `tfe_tensor_f64_*`
278///   creation function that has not yet been released.
279/// - `status` must be a valid, non-null pointer.
280///
281/// # Examples (C)
282///
283/// ```c
284/// tfe_tensor_f64 *copy = tfe_tensor_f64_clone(original, &status);
285/// tfe_tensor_f64_release(copy);
286/// ```
287#[no_mangle]
288pub unsafe extern "C" fn tfe_tensor_f64_clone(
289    _tensor: *const TfeTensorF64,
290    _status: *mut tfe_status_t,
291) -> *mut TfeTensorF64 {
292    todo!()
293}
294
295/// Release (free) a tensor.
296///
297/// After this call, `tensor` is invalid and must not be used.
298/// Passing a null pointer is a no-op.
299///
300/// For tensors imported via DLPack, this calls the DLPack deleter
301/// to notify the external owner that the data is no longer needed.
302///
303/// # Safety
304///
305/// `tensor` must be null or a valid pointer returned by a
306/// `tfe_tensor_f64_*` creation function that has not yet been released.
307#[no_mangle]
308pub unsafe extern "C" fn tfe_tensor_f64_release(_tensor: *mut TfeTensorF64) {
309    todo!()
310}
311
312/// Return the number of dimensions (rank) of the tensor.
313///
314/// # Safety
315///
316/// `tensor` must be a valid, non-null tensor pointer.
317#[no_mangle]
318pub unsafe extern "C" fn tfe_tensor_f64_ndim(_tensor: *const TfeTensorF64) -> usize {
319    todo!()
320}
321
322/// Write the shape of the tensor into the caller-provided buffer.
323///
324/// The caller must allocate `out_shape` with at least
325/// `tfe_tensor_f64_ndim(tensor)` elements.
326///
327/// # Safety
328///
329/// - `tensor` must be a valid, non-null tensor pointer.
330/// - `out_shape` must point to a buffer with at least `ndim` `usize` slots.
331///
332/// # Examples (C)
333///
334/// ```c
335/// size_t ndim = tfe_tensor_f64_ndim(t);
336/// size_t *shape = malloc(ndim * sizeof(size_t));
337/// tfe_tensor_f64_shape(t, shape);
338/// // shape now contains the dimensions
339/// free(shape);
340/// ```
341#[no_mangle]
342pub unsafe extern "C" fn tfe_tensor_f64_shape(
343    _tensor: *const TfeTensorF64,
344    _out_shape: *mut usize,
345) {
346    todo!()
347}
348
349/// Return the total number of elements in the tensor.
350///
351/// # Safety
352///
353/// `tensor` must be a valid, non-null tensor pointer.
354#[no_mangle]
355pub unsafe extern "C" fn tfe_tensor_f64_len(_tensor: *const TfeTensorF64) -> usize {
356    todo!()
357}
358
359/// Return a pointer to the tensor's raw data buffer.
360///
361/// The pointer is valid until `tfe_tensor_f64_release` is called on
362/// the tensor.
363///
364/// # Safety
365///
366/// `tensor` must be a valid, non-null tensor pointer. The returned
367/// pointer must not be used after `tfe_tensor_f64_release(tensor)`.
368///
369/// # Examples (C)
370///
371/// ```c
372/// const double *ptr = tfe_tensor_f64_data(t);
373/// size_t n = tfe_tensor_f64_len(t);
374/// for (size_t i = 0; i < n; i++) {
375///     printf("%f ", ptr[i]);
376/// }
377/// ```
378#[no_mangle]
379pub unsafe extern "C" fn tfe_tensor_f64_data(_tensor: *const TfeTensorF64) -> *const f64 {
380    todo!()
381}
382
383// ============================================================================
384// DLPack interop
385// ============================================================================
386
387/// Export a tensor as a DLPack managed tensor (zero-copy).
388///
389/// The tensor handle is **consumed** by this call and must not be
390/// used afterwards (do not call `tfe_tensor_f64_release` on it).
391///
392/// The returned `DLManagedTensorVersioned` must be consumed by the
393/// caller (e.g., passed to Julia `DLPack.from_dlpack()` or Python
394/// `numpy.from_dlpack()`). The consumer must call the `deleter`
395/// callback when done with the data.
396///
397/// If the tensor is NULL, returns NULL and sets status to `TFE_INVALID_ARGUMENT`.
398///
399/// # Safety
400///
401/// - `tensor` must be a valid tensor pointer or NULL.
402/// - `status` must be a valid, non-null pointer.
403///
404/// # Examples (C)
405///
406/// ```c
407/// tfe_status_t status;
408/// tfe_tensor_f64 *t = tfe_tensor_f64_zeros(shape, 2, &status);
409///
410/// // Export to DLPack (tensor handle is consumed)
411/// DLManagedTensorVersioned *dl = tfe_tensor_f64_to_dlpack(t, &status);
412/// // t is now invalid — do NOT call tfe_tensor_f64_release(t)
413///
414/// // Pass dl to Julia/Python, which calls dl->deleter(dl) when done
415/// ```
416#[no_mangle]
417pub unsafe extern "C" fn tfe_tensor_f64_to_dlpack(
418    _tensor: *mut TfeTensorF64,
419    _status: *mut tfe_status_t,
420) -> *mut DLManagedTensorVersioned {
421    todo!()
422}
423
424/// Import a DLPack managed tensor as a tenferro tensor (zero-copy).
425///
426/// Takes ownership of the `DLManagedTensorVersioned`. The deleter
427/// callback will be called when the returned tensor is released
428/// via `tfe_tensor_f64_release`.
429///
430/// The tensor data is NOT copied. The returned tensor references
431/// the same memory as the DLPack tensor.
432///
433/// Currently only `kDLCPU` device and float64 dtype are accepted
434/// (POC phase). Returns NULL with `TFE_INVALID_ARGUMENT` for other
435/// device types or dtypes.
436///
437/// # Safety
438///
439/// - `managed` must be a valid pointer to a `DLManagedTensorVersioned`.
440/// - The DLPack tensor's data must remain valid until the deleter is called.
441/// - `status` must be a valid, non-null pointer.
442///
443/// # Examples (C)
444///
445/// ```c
446/// // Obtain DLManagedTensorVersioned* from Julia/Python
447/// DLManagedTensorVersioned *dl = /* ... */;
448///
449/// tfe_status_t status;
450/// tfe_tensor_f64 *t = tfe_tensor_f64_from_dlpack(dl, &status);
451/// // dl is now owned by t — do NOT call dl->deleter(dl)
452///
453/// // Use t in einsum, SVD, etc.
454/// tfe_tensor_f64_release(t); // calls DLPack deleter internally
455/// ```
456#[no_mangle]
457pub unsafe extern "C" fn tfe_tensor_f64_from_dlpack(
458    _managed: *mut DLManagedTensorVersioned,
459    _status: *mut tfe_status_t,
460) -> *mut TfeTensorF64 {
461    todo!()
462}
463
464// ============================================================================
465// Einsum
466// ============================================================================
467
468/// Execute einsum using string notation.
469///
470/// Returns a new tensor. The caller must release it with
471/// `tfe_tensor_f64_release`.
472///
473/// # Safety
474///
475/// - `subscripts` must be a valid null-terminated C string.
476/// - `operands` must point to an array of `num_operands` valid tensor pointers.
477/// - `status` must be a valid, non-null pointer.
478///
479/// # Examples (C)
480///
481/// ```c
482/// const tfe_tensor_f64 *ops[] = {a, b};
483/// tfe_status_t status;
484/// tfe_tensor_f64 *c = tfe_einsum_f64("ij,jk->ik", ops, 2, &status);
485/// assert(status == TFE_SUCCESS);
486/// tfe_tensor_f64_release(c);
487/// ```
488#[no_mangle]
489pub unsafe extern "C" fn tfe_einsum_f64(
490    _subscripts: *const c_char,
491    _operands: *const *const TfeTensorF64,
492    _num_operands: usize,
493    _status: *mut tfe_status_t,
494) -> *mut TfeTensorF64 {
495    todo!()
496}
497
498/// Reverse-mode rule (VJP) for einsum.
499///
500/// Computes one gradient tensor per input operand given the output
501/// cotangent. The caller must provide `grads_out` as a pre-allocated
502/// array of `num_operands` pointers. Each returned tensor must be
503/// released by the caller.
504///
505/// # Safety
506///
507/// - `subscripts` must be a valid null-terminated C string.
508/// - `operands` must point to an array of `num_operands` valid tensor pointers.
509/// - `cotangent` must be a valid, non-null tensor pointer.
510/// - `grads_out` must point to a caller-allocated array of `num_operands`
511///   mutable `*mut TfeTensorF64` pointers.
512/// - `status` must be a valid, non-null pointer.
513///
514/// # Examples (C)
515///
516/// ```c
517/// // After computing c = einsum("ij,jk->ik", [a, b]):
518/// tfe_tensor_f64 *grads[2];
519/// tfe_status_t status;
520/// const tfe_tensor_f64 *ops[] = {a, b};
521/// tfe_einsum_rrule_f64("ij,jk->ik", ops, 2, grad_c, grads, &status);
522/// // grads[0] = gradient w.r.t. a
523/// // grads[1] = gradient w.r.t. b
524/// tfe_tensor_f64_release(grads[0]);
525/// tfe_tensor_f64_release(grads[1]);
526/// ```
527#[no_mangle]
528pub unsafe extern "C" fn tfe_einsum_rrule_f64(
529    _subscripts: *const c_char,
530    _operands: *const *const TfeTensorF64,
531    _num_operands: usize,
532    _cotangent: *const TfeTensorF64,
533    _grads_out: *mut *mut TfeTensorF64,
534    _status: *mut tfe_status_t,
535) {
536    todo!()
537}
538
539/// Forward-mode rule (JVP) for einsum.
540///
541/// Returns the output tangent. Elements of `tangents` may be null
542/// (interpreted as zero tangent for that operand).
543///
544/// # Safety
545///
546/// - `subscripts` must be a valid null-terminated C string.
547/// - `primals` must point to an array of `num_operands` valid tensor pointers.
548/// - `tangents` must point to an array of `num_operands` tensor pointers
549///   (elements may be null).
550/// - `status` must be a valid, non-null pointer.
551///
552/// # Examples (C)
553///
554/// ```c
555/// const tfe_tensor_f64 *primals[] = {a, b};
556/// const tfe_tensor_f64 *tangents[] = {da, NULL};  // no tangent for b
557/// tfe_status_t status;
558/// tfe_tensor_f64 *dc = tfe_einsum_frule_f64(
559///     "ij,jk->ik", primals, 2, tangents, &status);
560/// tfe_tensor_f64_release(dc);
561/// ```
562#[no_mangle]
563pub unsafe extern "C" fn tfe_einsum_frule_f64(
564    _subscripts: *const c_char,
565    _primals: *const *const TfeTensorF64,
566    _num_operands: usize,
567    _tangents: *const *const TfeTensorF64,
568    _status: *mut tfe_status_t,
569) -> *mut TfeTensorF64 {
570    todo!()
571}
572
573// ============================================================================
574// SVD
575// ============================================================================
576
577/// Compute the SVD of a tensor.
578///
579/// Decomposes the tensor into `U * diag(S) * Vt` after matricizing
580/// according to `left`/`right` dimension indices. Returns the three
581/// factors via output pointers. The caller must release each.
582///
583/// Set `max_rank` to 0 for no rank limit.
584/// Set `cutoff` to a negative value for no cutoff.
585///
586/// # Safety
587///
588/// - `tensor` must be a valid, non-null tensor pointer.
589/// - `left` must point to `left_len` valid `usize` values.
590/// - `right` must point to `right_len` valid `usize` values.
591/// - `u_out`, `s_out`, `vt_out` must be valid, non-null pointers to
592///   `*mut TfeTensorF64`.
593/// - `status` must be a valid, non-null pointer.
594///
595/// # Examples (C)
596///
597/// ```c
598/// size_t left[] = {0};
599/// size_t right[] = {1};
600/// tfe_tensor_f64 *u, *s, *vt;
601/// tfe_status_t status;
602/// tfe_svd_f64(a, left, 1, right, 1, 0, -1.0, &u, &s, &vt, &status);
603/// assert(status == TFE_SUCCESS);
604/// tfe_tensor_f64_release(u);
605/// tfe_tensor_f64_release(s);
606/// tfe_tensor_f64_release(vt);
607/// ```
608#[no_mangle]
609pub unsafe extern "C" fn tfe_svd_f64(
610    _tensor: *const TfeTensorF64,
611    _left: *const usize,
612    _left_len: usize,
613    _right: *const usize,
614    _right_len: usize,
615    _max_rank: usize,
616    _cutoff: f64,
617    _u_out: *mut *mut TfeTensorF64,
618    _s_out: *mut *mut TfeTensorF64,
619    _vt_out: *mut *mut TfeTensorF64,
620    _status: *mut tfe_status_t,
621) {
622    todo!()
623}
624
625/// Reverse-mode rule (VJP) for SVD.
626///
627/// Computes the gradient of the input tensor given cotangents for
628/// U, S, and Vt. Any cotangent may be null (zero cotangent).
629///
630/// # Safety
631///
632/// - `tensor` must be a valid, non-null tensor pointer.
633/// - `left` must point to `left_len` valid `usize` values.
634/// - `right` must point to `right_len` valid `usize` values.
635/// - `cotangent_u`, `cotangent_s`, `cotangent_vt` may each be null.
636/// - `status` must be a valid, non-null pointer.
637///
638/// # Examples (C)
639///
640/// ```c
641/// size_t left[] = {0};
642/// size_t right[] = {1};
643/// tfe_status_t status;
644/// // Only need gradient through singular values
645/// tfe_tensor_f64 *grad = tfe_svd_rrule_f64(
646///     a, left, 1, right, 1, 0, -1.0,
647///     NULL,    // no cotangent for U
648///     cot_s,   // cotangent for S
649///     NULL,    // no cotangent for Vt
650///     &status);
651/// tfe_tensor_f64_release(grad);
652/// ```
653#[no_mangle]
654pub unsafe extern "C" fn tfe_svd_rrule_f64(
655    _tensor: *const TfeTensorF64,
656    _left: *const usize,
657    _left_len: usize,
658    _right: *const usize,
659    _right_len: usize,
660    _max_rank: usize,
661    _cutoff: f64,
662    _cotangent_u: *const TfeTensorF64,
663    _cotangent_s: *const TfeTensorF64,
664    _cotangent_vt: *const TfeTensorF64,
665    _status: *mut tfe_status_t,
666) -> *mut TfeTensorF64 {
667    todo!()
668}
669
670/// Forward-mode rule (JVP) for SVD.
671///
672/// Computes tangents for U, S, Vt given an input tangent.
673/// The `tangent` parameter may be null (zero tangent).
674///
675/// # Safety
676///
677/// - `tensor` must be a valid, non-null tensor pointer.
678/// - `left` must point to `left_len` valid `usize` values.
679/// - `right` must point to `right_len` valid `usize` values.
680/// - `tangent` may be null (zero tangent).
681/// - `u_out`, `s_out`, `vt_out` must be valid, non-null pointers to
682///   `*mut TfeTensorF64`.
683/// - `status` must be a valid, non-null pointer.
684///
685/// # Examples (C)
686///
687/// ```c
688/// size_t left[] = {0};
689/// size_t right[] = {1};
690/// tfe_tensor_f64 *du, *ds, *dvt;
691/// tfe_status_t status;
692/// tfe_svd_frule_f64(
693///     a, left, 1, right, 1, 0, -1.0,
694///     da, &du, &ds, &dvt, &status);
695/// tfe_tensor_f64_release(du);
696/// tfe_tensor_f64_release(ds);
697/// tfe_tensor_f64_release(dvt);
698/// ```
699#[no_mangle]
700pub unsafe extern "C" fn tfe_svd_frule_f64(
701    _tensor: *const TfeTensorF64,
702    _left: *const usize,
703    _left_len: usize,
704    _right: *const usize,
705    _right_len: usize,
706    _max_rank: usize,
707    _cutoff: f64,
708    _tangent: *const TfeTensorF64,
709    _u_out: *mut *mut TfeTensorF64,
710    _s_out: *mut *mut TfeTensorF64,
711    _vt_out: *mut *mut TfeTensorF64,
712    _status: *mut tfe_status_t,
713) {
714    todo!()
715}