This is an automated email from the ASF dual-hosted git repository.

alamb pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/arrow-rs.git


The following commit(s) were added to refs/heads/main by this push:
     new f220d72472 feat(arrow-schema): expose FixedShapeTensor logical shape 
(#11249)
f220d72472 is described below

commit f220d72472e652e555a2d73d4a9cad48c78d7397
Author: yongster <[email protected]>
AuthorDate: Fri Oct 2 02:05:42 2026 +0800

    feat(arrow-schema): expose FixedShapeTensor logical shape (#11249)
    
    # Which issue does this PR close?
    
    - Closes #11248.
    
    # Rationale for this change
    
    `FixedShapeTensor` stores a physical shape and an optional permutation,
    but does not expose the logical shape. The extension defines logical
    dimension `i` as physical dimension `permutations[i]`. Physical shape
    `[100, 200, 500]` with permutation `[2, 0, 1]` is logical shape `[500,
    100, 200]`.
    
    This is independent of #11247, which exposes the physical shape. The two
    accessors answer different questions.
    
    # What changes are included in this PR?
    
    - `FixedShapeTensor::logical_shape(&self) -> Vec<usize>`
    - `FixedShapeTensorMetadata::logical_shape(&self) -> Vec<usize>`
    - Without a permutation, the result is the physical shape
    - A doc example for `[100, 200, 500]` / `[2, 0, 1]`, plus tests for no
    permutation, that permutation, and an identity permutation
    
    Serialization and validation are unchanged. This does not rename the
    metadata key `permutations`.
    
    # Are these changes tested?
    
    Yes.
    
    - `cargo test -p arrow-schema --features canonical_extension_types --lib
    logical_shape`
    - `cargo test -p arrow-schema --doc --features canonical_extension_types
    logical_shape`
    - `cargo clippy -p arrow-schema --features canonical_extension_types
    --all-targets --no-deps -- -D warnings`
    
    # Are there any user-facing changes?
    
    Additive API only, behind the existing `canonical_extension_types`
    feature. No breaking change.
    
    AI assistance: the accessor and tests were drafted with AI and checked
    against the permutation rule already documented on `FixedShapeTensor`.
    
    Co-authored-by: yang3.xie <[email protected]>
    Co-authored-by: Andrew Lamb <[email protected]>
---
 .../src/extension/canonical/fixed_shape_tensor.rs  | 55 ++++++++++++++++++++++
 1 file changed, 55 insertions(+)

diff --git a/arrow-schema/src/extension/canonical/fixed_shape_tensor.rs 
b/arrow-schema/src/extension/canonical/fixed_shape_tensor.rs
index bf85d53fe5..8e8f71b922 100644
--- a/arrow-schema/src/extension/canonical/fixed_shape_tensor.rs
+++ b/arrow-schema/src/extension/canonical/fixed_shape_tensor.rs
@@ -146,6 +146,30 @@ impl FixedShapeTensor {
     pub fn permutations(&self) -> Option<&[usize]> {
         self.metadata.permutations()
     }
+
+    /// Returns the logical shape.
+    ///
+    /// Logical dimension `i` has the size of physical dimension
+    /// `permutations[i]`. Without a permutation, this is the physical shape.
+    ///
+    /// Physical shape `[100, 200, 500]` and permutation `[2, 0, 1]` have
+    /// logical shape `[500, 100, 200]`.
+    ///
+    /// ```
+    /// # use arrow_schema::extension::FixedShapeTensor;
+    /// # use arrow_schema::DataType;
+    /// let tensor = FixedShapeTensor::try_new(
+    ///     DataType::Float32,
+    ///     [100, 200, 500],
+    ///     None,
+    ///     Some(vec![2, 0, 1]),
+    /// )
+    /// .unwrap();
+    /// assert_eq!(tensor.logical_shape(), vec![500, 100, 200]);
+    /// ```
+    pub fn logical_shape(&self) -> Vec<usize> {
+        self.metadata.logical_shape()
+    }
 }
 
 /// Extension type metadata for [`FixedShapeTensor`].
@@ -377,6 +401,16 @@ impl FixedShapeTensorMetadata {
     pub fn permutations(&self) -> Option<&[usize]> {
         self.permutations.as_ref().map(AsRef::as_ref)
     }
+
+    /// Returns the logical shape.
+    ///
+    /// See [`FixedShapeTensor::logical_shape`].
+    pub fn logical_shape(&self) -> Vec<usize> {
+        match &self.permutations {
+            Some(permutation) => permutation.iter().map(|&index| 
self.shape[index]).collect(),
+            None => self.shape.clone(),
+        }
+    }
 }
 
 impl ExtensionType for FixedShapeTensor {
@@ -494,6 +528,27 @@ mod tests {
         Ok(())
     }
 
+    #[test]
+    fn logical_shape_follows_permutation() -> Result<(), ArrowError> {
+        let physical = FixedShapeTensor::try_new(DataType::Float32, [100, 200, 
500], None, None)?;
+        assert_eq!(physical.logical_shape(), vec![100, 200, 500]);
+        assert_eq!(physical.metadata().logical_shape(), vec![100, 200, 500]);
+
+        let permuted = FixedShapeTensor::try_new(
+            DataType::Float32,
+            [100, 200, 500],
+            Some(vec!["C".to_owned(), "H".to_owned(), "W".to_owned()]),
+            Some(vec![2, 0, 1]),
+        )?;
+        assert_eq!(permuted.logical_shape(), vec![500, 100, 200]);
+        assert_eq!(permuted.metadata().logical_shape(), vec![500, 100, 200]);
+
+        let identity =
+            FixedShapeTensor::try_new(DataType::Float32, [2, 6], None, 
Some(vec![0, 1]))?;
+        assert_eq!(identity.logical_shape(), vec![2, 6]);
+        Ok(())
+    }
+
     #[test]
     fn shape_distinguishes_equal_sizes() -> Result<(), ArrowError> {
         let wide = FixedShapeTensor::try_new(DataType::Float32, [2, 6], None, 
None)?;

Reply via email to