@@ -20,6 +20,7 @@ use ndslice::view::Region;
2020use  serde:: Deserialize ; 
2121use  serde:: Serialize ; 
2222
23+ mod  rle; 
2324mod  value_overlay; 
2425pub  use  value_overlay:: BuildError ; 
2526pub  use  value_overlay:: ValueOverlay ; 
@@ -748,6 +749,95 @@ impl<T: PartialEq + Clone> ValueMesh<T> {
748749     pub  fn  compress_adjacent_in_place ( & mut  self )  { 
749750        self . compress_adjacent_in_place_by ( |a,  b| a == b) 
750751    } 
752+ 
753+     /// Materializes this mesh into a vector of `(Range<usize>, T)` 
754+      /// runs. 
755+      /// 
756+      /// For a dense representation, this walks the value vector and 
757+      /// groups adjacent equal values into contiguous runs. The result 
758+      /// is equivalent to what would be stored in a compressed 
759+      /// representation, but the mesh itself is not mutated or 
760+      /// re-encoded. This is purely a read-only view. 
761+      /// 
762+      /// For a compressed representation, the stored runs are simply 
763+      /// cloned. 
764+      /// 
765+      /// This method is intended for inspection, testing, and 
766+      /// diff/merge operations that need a uniform view of value runs 
767+      /// without changing the underlying representation. 
768+      fn  materialized_runs ( & self )  -> Vec < ( Range < usize > ,  T ) >  { 
769+         match  & self . rep  { 
770+             Rep :: Dense  {  values }  => { 
771+                 // Coalesce adjacent equals into runs. 
772+                 let  mut  out = Vec :: new ( ) ; 
773+                 if  values. is_empty ( )  { 
774+                     return  out; 
775+                 } 
776+                 let  mut  start = 0usize ; 
777+                 for  i in  1 ..values. len ( )  { 
778+                     if  values[ i]  != values[ i - 1 ]  { 
779+                         out. push ( ( start..i,  values[ i - 1 ] . clone ( ) ) ) ; 
780+                         start = i; 
781+                     } 
782+                 } 
783+                 out. push ( ( start..values. len ( ) ,  values. last ( ) . unwrap ( ) . clone ( ) ) ) ; 
784+                 out
785+             } 
786+             Rep :: Compressed  {  table,  runs }  => runs
787+                 . iter ( ) 
788+                 . map ( |r| { 
789+                     let  id = r. id  as  usize ; 
790+                     ( ( r. start  as  usize ..r. end  as  usize ) ,  table[ id] . clone ( ) ) 
791+                 } ) 
792+                 . collect ( ) , 
793+         } 
794+     } 
795+ } 
796+ 
797+ impl < T :  Clone  + Eq >  ValueMesh < T >  { 
798+     /// Merge a sparse overlay into this mesh. 
799+      /// 
800+      /// Overlay segments are applied with **last-writer-wins** 
801+      /// precedence on overlap (identical to `RankedValues::merge_from` 
802+      /// behavior). The result is stored compressed. 
803+      pub  fn  merge_from_overlay ( 
804+         & mut  self , 
805+         region :  & ndslice:: view:: Region , 
806+         overlay :  & ValueOverlay < T > , 
807+     )  -> Result < ( ) ,  BuildError >  { 
808+         let  n = region. num_ranks ( ) ; 
809+ 
810+         // Bounds validation (structure already validated by 
811+         // ValueOverlay). 
812+         for  ( r,  _)  in  overlay. runs ( )  { 
813+             if  r. end  > n { 
814+                 return  Err ( BuildError :: OutOfBounds  { 
815+                     range :  r. clone ( ) , 
816+                     region_len :  n, 
817+                 } ) ; 
818+             } 
819+         } 
820+ 
821+         // Left: current mesh as normalized value-bearing runs. 
822+         let  left = self . materialized_runs ( ) ; 
823+         // Right: overlay runs (already sorted, non-overlapping, 
824+         // coalesced). 
825+         let  right:  Vec < ( std:: ops:: Range < usize > ,  T ) >  = overlay. runs ( ) . cloned ( ) . collect ( ) ; 
826+ 
827+         // Merge with overlay precedence, reusing the same splitting 
828+         // strategy as RankedValues::merge_from. 
829+         let  merged = rle:: merge_value_runs ( left,  right) ; 
830+ 
831+         // Re-encode to compressed representation: 
832+         let  ( table,  raw_runs)  = rle:: rle_from_value_runs ( merged) ; 
833+         let  runs = raw_runs
834+             . into_iter ( ) 
835+             . map ( |( r,  id) | Run :: new ( r. start ,  r. end ,  id) ) 
836+             . collect ( ) ; 
837+         self . rep  = Rep :: Compressed  {  table,  runs } ; 
838+ 
839+         Ok ( ( ) ) 
840+     } 
751841} 
752842
753843impl < T :  Clone >  ValueMesh < T >  { 
@@ -782,72 +872,15 @@ impl<T: Clone> ValueMesh<T> {
782872            Rep :: Dense  {  values }  => std:: mem:: take ( values) , 
783873            Rep :: Compressed  {  .. }  => return , 
784874        } ; 
785-         let  ( table,  runs)  = compress_adjacent_with ( values,  same) ; 
875+         let  ( table,  raw_runs)  = rle:: rle_from_dense ( values,  same) ; 
876+         let  runs = raw_runs
877+             . into_iter ( ) 
878+             . map ( |( r,  id) | Run :: new ( r. start ,  r. end ,  id) ) 
879+             . collect ( ) ; 
786880        self . rep  = Rep :: Compressed  {  table,  runs } ; 
787881    } 
788882} 
789883
790- /// Performs simple run-length encoding (RLE) compression over a dense 
791- /// sequence of values. 
792- /// 
793- /// Adjacent "equal" elements are coalesced into contiguous runs, 
794- /// producing: 
795- /// 
796- /// - a **table** of unique values (in first-occurrence order) 
797- /// - a **run list** of `(range, id)` pairs, where `range` is the 
798- ///   half-open index range `[start, end)` in the original dense 
799- ///   array, and `id` indexes into `table`. 
800- /// 
801- /// # Example 
802- /// ``` 
803- /// // Input: [A, A, B, B, B, A] 
804- /// // Output: 
805- /// // table = [A, B, A] 
806- /// // runs  = [(0..2, 0), (2..5, 1), (5..6, 2)] 
807- /// ``` 
808- /// 
809- /// # Requirements 
810- /// - `T: Clone` is required to copy elements into the table. 
811- /// 
812- /// # Returns 
813- /// A tuple `(table, runs)` that together form the compressed 
814- /// representation. Expanding the runs reproduces the original data. 
815- fn  compress_adjacent_with < T :  Clone ,  F > ( values :  Vec < T > ,  mut  same :  F )  -> ( Vec < T > ,  Vec < Run > ) 
816- where 
817-     F :  FnMut ( & T ,  & T )  -> bool , 
818- { 
819-     // Empty input; trivial empty compression. 
820-     if  values. is_empty ( )  { 
821-         return  ( Vec :: new ( ) ,  Vec :: new ( ) ) ; 
822-     } 
823- 
824-     let  mut  table = Vec :: new ( ) ;  // unique values 
825-     let  mut  runs = Vec :: new ( ) ;  // (range, table_id) pairs 
826- 
827-     let  mut  start = 0usize ; 
828-     table. push ( values[ 0 ] . clone ( ) ) ; 
829-     let  mut  cur_id:  u32  = 0 ; 
830- 
831-     // Walk through all subsequent elements, closing and opening runs 
832-     // whenever the value changes. 
833-     for  ( i,  _value)  in  values. iter ( ) . enumerate ( ) . skip ( 1 )  { 
834-         if  !same ( & values[ i] ,  & table[ cur_id as  usize ] )  { 
835-             // Close current run [start, i) 
836-             runs. push ( Run :: new ( start,  i,  cur_id) ) ; 
837- 
838-             // Start a new run 
839-             start = i; 
840-             table. push ( values[ i] . clone ( ) ) ; 
841-             cur_id = ( table. len ( )  - 1 )  as  u32 ; 
842-         } 
843-     } 
844- 
845-     // Close the final run 
846-     runs. push ( Run :: new ( start,  values. len ( ) ,  cur_id) ) ; 
847- 
848-     ( table,  runs) 
849- } 
850- 
851884#[ cfg( test) ]  
852885mod  tests { 
853886    use  std:: convert:: Infallible ; 
@@ -1681,4 +1714,79 @@ mod tests {
16811714        assert_eq ! ( mesh. get( 3 ) ,  Some ( & 2 ) ) ; 
16821715        assert_eq ! ( mesh. get( 5 ) ,  Some ( & 3 ) ) ; 
16831716    } 
1717+ 
1718+     #[ test]  
1719+     fn  merge_from_overlay_basic ( )  { 
1720+         // Base mesh with two contiguous runs. 
1721+         let  region:  Region  = extent ! ( n = 8 ) . into ( ) ; 
1722+         let  mut  mesh = ValueMesh :: from_dense ( region. clone ( ) ,  vec ! [ 1 ,  1 ,  1 ,  2 ,  2 ,  2 ,  3 ,  3 ] ) . unwrap ( ) ; 
1723+ 
1724+         // Overlay replaces middle segment [2..6) with 9s. 
1725+         let  overlay = ValueOverlay :: try_from_runs ( vec ! [ ( 2 ..6 ,  9 ) ] ) . unwrap ( ) ; 
1726+ 
1727+         mesh. merge_from_overlay ( & region,  & overlay) . unwrap ( ) ; 
1728+ 
1729+         // Materialize back into ranges to inspect. 
1730+         let  out = mesh. materialized_runs ( ) ; 
1731+ 
1732+         // Expected: left prefix (0..2)=1, replaced middle (2..6)=9, tail (6..8)=3. 
1733+         assert_eq ! ( out,  vec![ ( 0 ..2 ,  1 ) ,  ( 2 ..6 ,  9 ) ,  ( 6 ..8 ,  3 ) ] ) ; 
1734+     } 
1735+ 
1736+     #[ test]  
1737+     fn  merge_from_overlay_multiple_spans ( )  { 
1738+         // Build mesh with alternating runs. 
1739+         let  region:  Region  = extent ! ( m = 12 ) . into ( ) ; 
1740+         let  mut  mesh =
1741+             ValueMesh :: from_dense ( region. clone ( ) ,  vec ! [ 1 ,  1 ,  1 ,  2 ,  2 ,  2 ,  3 ,  3 ,  3 ,  4 ,  4 ,  4 ] ) 
1742+                 . unwrap ( ) ; 
1743+ 
1744+         // Overlay has a run that spans across the boundary of two 
1745+         // left runs and another disjoint run later. 
1746+         let  overlay = ValueOverlay :: try_from_runs ( vec ! [ ( 2 ..6 ,  9 ) ,  ( 9 ..11 ,  8 ) ] ) . unwrap ( ) ; 
1747+ 
1748+         mesh. merge_from_overlay ( & region,  & overlay) . unwrap ( ) ; 
1749+         let  out = mesh. materialized_runs ( ) ; 
1750+ 
1751+         // Expected after merge and re-compression: 
1752+         // (0..2,1) untouched 
1753+         // (2..6,9) overwrite of part of [1,2] runs 
1754+         // (6..9,3) left tail survives 
1755+         // (9..11,8) overwrite inside [4] run 
1756+         // (11..12,4) leftover tail 
1757+         assert_eq ! ( 
1758+             out, 
1759+             vec![ ( 0 ..2 ,  1 ) ,  ( 2 ..6 ,  9 ) ,  ( 6 ..9 ,  3 ) ,  ( 9 ..11 ,  8 ) ,  ( 11 ..12 ,  4 ) ] 
1760+         ) ; 
1761+     } 
1762+ 
1763+     #[ test]  
1764+     fn  merge_from_overlay_crosses_row_boundary ( )  { 
1765+         // 2 x 5 region -> 10 linear ranks in row-major order. 
1766+         let  region:  Region  = extent ! ( rows = 2 ,  cols = 5 ) . into ( ) ; 
1767+ 
1768+         // Dense values laid out row-major: 
1769+         // row 0: [1, 1, 1, 2, 2] 
1770+         // row 1: [3, 3, 4, 4, 4] 
1771+         let  mut  mesh =
1772+             ValueMesh :: from_dense ( region. clone ( ) ,  vec ! [ 1 ,  1 ,  1 ,  2 ,  2 ,  3 ,  3 ,  4 ,  4 ,  4 ] ) . unwrap ( ) ; 
1773+ 
1774+         // Overlay that crosses the row boundary: 
1775+         // linear ranks [3..7) -> 9 
1776+         //   - tail of row 0: indices 3,4 (the two 2s) 
1777+         //   - head of row 1: indices 5,6 (the two 3s) 
1778+         let  overlay = ValueOverlay :: try_from_runs ( vec ! [ ( 3 ..7 ,  9 ) ] ) . unwrap ( ) ; 
1779+ 
1780+         mesh. merge_from_overlay ( & region,  & overlay) . unwrap ( ) ; 
1781+ 
1782+         // After merge, the dense view should be: 
1783+         // [1,1,1, 9,9, 9,9, 4,4,4] 
1784+         let  flat:  Vec < _ >  = mesh. values ( ) . collect ( ) ; 
1785+         assert_eq ! ( flat,  vec![ 1 ,  1 ,  1 ,  9 ,  9 ,  9 ,  9 ,  4 ,  4 ,  4 ] ) ; 
1786+ 
1787+         // And the materialized runs should reflect that: 
1788+         // (0..3,1) | (3..7,9) | (7..10,4) 
1789+         let  runs = mesh. materialized_runs ( ) ; 
1790+         assert_eq ! ( runs,  vec![ ( 0 ..3 ,  1 ) ,  ( 3 ..7 ,  9 ) ,  ( 7 ..10 ,  4 ) ] ) ; 
1791+     } 
16841792} 
0 commit comments