diff --git a/ruminations/002-rumination.md b/ruminations/002-rumination.md index 0766b23..e00bdb4 100644 --- a/ruminations/002-rumination.md +++ b/ruminations/002-rumination.md @@ -965,27 +965,57 @@ All in all, that amounts to a swapping of the first two coordinate elements of t #### `stack roll` -Essentially, `roll=m,n` is a [big swap](https://stackoverflow.com/a/15997537/618276), essentially -flipping the `n` upper elements with the `m - n` lower, as seen from these examples: +Essentially, `roll=m,n` is a [big swap](https://stackoverflow.com/a/15997537/618276), hence +swapping the `n` upper elements with the `m - n` lower. -| Stack before | Instruction | Stack after | -|----------------|--------------------------------------------| -| 1,2,3,4 | roll=3,2 | 1,3,4,2 | -| 1,2,3,4 | roll=3,-2 | 1,3,4,2 | -| 1,3,4,2 | roll=3,1 | 1,2,3,4 | +If `n < 0`, the split between the lower and upper blocks is counted from the bottom of the +substack, by implicitly setting `n = m + n` before operating, as seen from these examples: -Note that the last example shows that `roll=m,m-n` is the opposite of `roll=m,n` +| Stack before | Instruction | Stack after | +| -------------- | ----------- | ---------------- | +| 1,2,3,4 | roll=3,-2 | 1,4,2,3 | +| 1,2,3,4 | roll=3,1 | 1,4,2,3 | +| 1,2,3,4 | roll=3,2 | 1,3,4,2 | +| 1,3,4,2 | roll=3,1 | 1,2,3,4 | + +Note that the first two examples show that for negative `n`, `roll=m,n` +is the same as `roll=m,m+n`, while the last two examples show that +`roll=m,m-n` is the opposite of `roll=m,n`. + +#### `stack unroll` + +For easier construction of "the opposite case", above, `stack unroll` +is the tool. Essentially, `unroll=m,n` is the same as `roll=m,m-n`, +i.e. a [big swap](https://stackoverflow.com/a/15997537/618276), +swapping the `n` *lower* elements with the `m - n` *upper*, +as seen from these examples: + +| Stack before | Instruction | Stack after | +| -------------- | ------------ | --------------- | +| 1,2,3,4 | unroll=3,2 | 1,4,2,3 | +| 1,2,3,4 | unroll=3,-2 | 1,3,4,2 | +| 1,3,4,2 | unroll=3,2 | 1,2,3,4 | +| 1,2,3,4 | roll=3,2 | 1,3,4,2 | +| 1,3,4,2 | unroll=3,2 | 1,2,3,4 | + +Note that the last example shows that `unroll=m,n` is the opposite of `roll=m,n` #### Inverse operation `stack` does not support the `inv` modifier. Instead use these substitutions: | Forward | Inverse | -|---------|-----------| +| ------- | --------- | | push | pop | | pop | push | | swap | swap | | roll=m,n| roll=m,m-n| +| roll=m,n| unroll=m,n| + +#### Swapping two 2D coordinates packed in a 4D + +- `stack push=1,2,3,4 | stack roll=4,2 | stack pop=2,1,4,3` or +- `stack push=1,2,3,4 | stack pop=4,3,2,1` **See also:** [`pop`](#operator-pop) (deprecated), [`push`](#operator-push) (deprecated) diff --git a/src/inner_op/stack.rs b/src/inner_op/stack.rs index f933df4..3a52612 100644 --- a/src/inner_op/stack.rs +++ b/src/inner_op/stack.rs @@ -3,10 +3,11 @@ use crate::authoring::*; // NOTE: roll and drop are not implemented yet #[rustfmt::skip] -pub const STACK_GAMUT: [OpParameter; 5] = [ +pub const STACK_GAMUT: [OpParameter; 6] = [ OpParameter::Series { key: "push", default: Some("") }, OpParameter::Series { key: "pop", default: Some("") }, OpParameter::Series { key: "roll", default: Some("") }, + OpParameter::Series { key: "unroll", default: Some("") }, OpParameter::Flag { key: "swap" }, OpParameter::Flag { key: "drop" }, ]; @@ -56,12 +57,26 @@ pub fn new(parameters: &RawParameters, _ctx: &dyn Context) -> Result || roll_args[0] <= roll_args[1].abs() { return Err(Error::MissingParam( - "roll takes exactly two integer parameters, ´(m,n): |n| Result if subcommands_given != 1 { return Err(Error::MissingParam( - "stack: must specify exactly one of push/pop/roll/swap/drop".to_string(), + "stack: must specify exactly one of push/pop/roll/swap/unroll/drop".to_string(), )); } @@ -103,34 +118,24 @@ pub(super) fn stack_fwd( let successes = match action.as_str() { "push" => { - // Turn f64 dimensions 1-4 into usize indices 0-3 - let args: Vec = params - .series("push") - .unwrap() - .iter() - .map(|i| *i as usize - 1) - .collect(); + let args = params.series_as_usize("push").unwrap(); stack_push(stack, operands, &args) } "pop" => { - // Turn f64 dimensions 1-4 into usize indices 0-3 - let args: Vec = params - .series("pop") - .unwrap() - .iter() - .map(|i| *i as usize - 1) - .collect(); + let args = params.series_as_usize("pop").unwrap(); stack_pop(stack, operands, &args) } "roll" => { - let args: Vec = params - .series("roll") - .unwrap() - .iter() - .map(|i| *i as i64) - .collect(); + let args = params.series_as_i64("roll").unwrap(); + stack_roll(stack, operands, &args) + } + + "unroll" => { + let mut args = params.series_as_i64("unroll").unwrap(); + args[1] = args[0] - args[1]; + dbg!(&args); stack_roll(stack, operands, &args) } @@ -165,38 +170,28 @@ pub(super) fn stack_inv( }; let successes = match action.as_str() { + // An inverse push is a pop with reversed args "push" => { - // Turn f64 dimensions 1-4 into **reversed** usize indices 0-3 ****** - let args: Vec = params - .series("push") - .unwrap() - .iter() - .rev() - .map(|i| *i as usize - 1) - .collect(); + let mut args = params.series_as_usize("push").unwrap(); + args.reverse(); stack_pop(stack, operands, &args) } + // And an inverse pop is a push with reversed args "pop" => { - // Turn f64 dimensions 1-4 into **reversed** usize indices 0-3 - let args: Vec = params - .series("pop") - .unwrap() - .iter() - .rev() - .map(|i| *i as usize - 1) - .collect(); - return stack_push(stack, operands, &args); + let mut args = params.series_as_usize("pop").unwrap(); + args.reverse(); + stack_push(stack, operands, &args) } "roll" => { - let mut args: Vec = params - .series("roll") - .unwrap() - .iter() - .map(|i| *i as i64) - .collect(); - args[1] = -args[1]; + let mut args = params.series_as_i64("roll").unwrap(); + args[1] = args[0] - args[1]; + stack_roll(stack, operands, &args) + } + + "unroll" => { + let args = params.series_as_i64("roll").unwrap(); stack_roll(stack, operands, &args) } @@ -234,7 +229,8 @@ fn stack_push( for i in 0..number_of_operands { let coord = operands.get_coord(i); for j in 0..number_of_pushes { - ext[j][i] = coord[args[j]]; + // args are 1 based so we adjust + ext[j][i] = coord[args[j] - 1]; } } @@ -245,8 +241,8 @@ fn stack_push( /// roll m,n: On the sub-stack consisting of the m upper elements, /// roll n elements from the top, to the bottom of the sub-stack. -/// Hence, roll is a "large flip", essentially flipping the n upper -/// elements with the m - n lower +/// Hence, roll is a "big swap", essentially swapping the n upper +/// elements with the m - n lower. fn stack_roll(stack: &mut Vec>, operands: &mut dyn CoordinateSet, args: &[i64]) -> usize { let m = args[0].abs(); let mut n = args[1]; @@ -294,21 +290,18 @@ fn stack_pop(stack: &mut Vec>, operands: &mut dyn CoordinateSet, args: } // Remove the correct number of elements and obtain a reversed version. - // Incidentally, this is both the easiest way to obtain the popped - // subset, and the easiest way to make the top-of-stack (i.e. the - // element first popped) have the index 0, which makes the 'for j...' - // loop below slightly more straightforward let mut ext = Vec::with_capacity(number_of_pops); for _ in args { ext.push(stack.pop().unwrap()); } - // Extract the required stack elements into the proper + // Inject the required stack elements into the proper // positions of the coordinate elements for i in 0..number_of_operands { let mut coord = operands.get_coord(i); for j in 0..number_of_pops { - coord[args[j]] = ext[j][i]; + // args are 1 based so we adjust + coord[args[j] - 1] = ext[j][i]; } operands.set_coord(i, &coord); } @@ -435,6 +428,64 @@ mod tests { assert_eq!(data[0][0], 14.); assert_eq!(data[0][1], 13.); + // Roundrip roll using the unroll syntactic sugar + let mut data = master_data.clone(); + let op = + ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack unroll=3,2 | stack pop=1,2")?; + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0][0], 14.); + assert_eq!(data[0][1], 13.); + + Ok(()) + } + + #[test] + fn stack_examples_from_rumination_002() -> Result<(), Error> { + let mut ctx = Minimal::default(); + let master_data = vec![Coor4D([1., 2., 3., 4.])]; + + // Roll + let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack pop=4,3,2,1")?; + let mut data = master_data.clone(); + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0].0, [1., 3., 4., 2.]); + + let op = ctx.op("stack push=1,2,3,4 | stack roll=3,-2 | stack pop=4,3,2,1")?; + let mut data = master_data.clone(); + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0].0, [1., 4., 2., 3.]); + + let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack pop=4,3,2,1")?; + let mut data = master_data.clone(); + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0].0, [1., 3., 4., 2.]); + let op = ctx.op("stack push=1,2,3,4 | stack roll=3,1 | stack pop=4,3,2,1")?; + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0].0, [1., 2., 3., 4.]); + + // Unroll + let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,2 | stack pop=4,3,2,1")?; + let mut data = master_data.clone(); + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0].0, [1., 4., 2., 3.]); + + let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,-2 | stack pop=4,3,2,1")?; + let mut data = master_data.clone(); + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0].0, [1., 3., 4., 2.]); + + let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,2 | stack pop=4,3,2,1")?; + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0].0, [1., 2., 3., 4.]); + + let op = ctx.op("stack push=1,2,3,4 | stack roll=3,2 | stack pop=4,3,2,1")?; + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0].0, [1., 3., 4., 2.]); + + let op = ctx.op("stack push=1,2,3,4 | stack unroll=3,2 | stack pop=4,3,2,1")?; + ctx.apply(op, Fwd, &mut data)?; + assert_eq!(data[0].0, [1., 2., 3., 4.]); + Ok(()) } } diff --git a/src/op/parsed_parameters.rs b/src/op/parsed_parameters.rs index e4a9d27..6e850af 100644 --- a/src/op/parsed_parameters.rs +++ b/src/op/parsed_parameters.rs @@ -59,48 +59,76 @@ impl ParsedParameters { pub fn boolean(&self, key: &str) -> bool { self.boolean.contains(key) } + pub fn natural(&self, key: &str) -> Result { if let Some(value) = self.natural.get(key) { return Ok(*value); } Err(Error::MissingParam(key.to_string())) } + pub fn integer(&self, key: &str) -> Result { if let Some(value) = self.integer.get(key) { return Ok(*value); } Err(Error::MissingParam(key.to_string())) } + pub fn real(&self, key: &str) -> Result { if let Some(value) = self.real.get(key) { return Ok(*value); } Err(Error::MissingParam(key.to_string())) } + pub fn series(&self, key: &str) -> Result<&[f64], Error> { if let Some(value) = self.series.get(key) { return Ok(value); } Err(Error::MissingParam(key.to_string())) } + + pub fn series_as_i64(&self, key: &str) -> Result, Error> { + let args: Vec = self + .series(key) + .unwrap() + .iter() + .map(|i| *i as i64) + .collect(); + Ok(args) + } + + pub fn series_as_usize(&self, key: &str) -> Result, Error> { + let args: Vec = self + .series(key) + .unwrap() + .iter() + .map(|i| *i as usize) + .collect(); + Ok(args) + } + pub fn text(&self, key: &str) -> Result { if let Some(value) = self.text.get(key) { return Ok(value.to_string()); } Err(Error::MissingParam(key.to_string())) } + pub fn texts(&self, key: &str) -> Result<&Vec, Error> { if let Some(value) = self.texts.get(key) { return Ok(value); } Err(Error::MissingParam(key.to_string())) } + pub fn uuid(&self, key: &str) -> Result { if let Some(value) = self.uuid.get(key) { return Ok(*value); } Err(Error::MissingParam(key.to_string())) } + pub fn fourier_coefficients(&self, key: &str) -> Result { if let Some(value) = self.fourier_coefficients.get(key) { return Ok(*value); @@ -110,6 +138,7 @@ impl ParsedParameters { pub fn ignored(&self) -> Vec { self.ignored.clone() } + pub fn ellps(&self, index: usize) -> Ellipsoid { // if 'ellps' was explicitly given, it will override 'ellps_0' if index == 0 { @@ -124,18 +153,23 @@ impl ParsedParameters { // If none of them existed, i.e. no defaults were given, we return the general default Ellipsoid::default() } + pub fn k(&self, index: usize) -> f64 { *(self.real.get(&format!("k_{index}")[..]).unwrap_or(&1.)) } + pub fn x(&self, index: usize) -> f64 { *(self.real.get(&format!("x_{index}")[..]).unwrap_or(&0.)) } + pub fn y(&self, index: usize) -> f64 { *(self.real.get(&format!("y_{index}")[..]).unwrap_or(&0.)) } + pub fn lat(&self, index: usize) -> f64 { *self.real.get(&format!("lat_{index}")[..]).unwrap_or(&0.) } + pub fn lon(&self, index: usize) -> f64 { *self.real.get(&format!("lon_{index}")[..]).unwrap_or(&0.) }