-
Notifications
You must be signed in to change notification settings - Fork 6
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
With the recent merge of @Rennzie's implementation of the PROJ unitconvert operator, we just need the (somewhat simpler) axisswap to support PROJ's way of doing what `adapt` otherwise does for RG.
- Loading branch information
1 parent
e7f8a4d
commit 8712c61
Showing
3 changed files
with
268 additions
and
4 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,219 @@ | ||
use super::*; | ||
|
||
// ----- F O R W A R D ----------------------------------------------------------------- | ||
|
||
fn fwd(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize { | ||
let n = operands.len(); | ||
|
||
// We default to order=1,2,3,4, so if order is not given, we are done already | ||
let Ok(order) = op.params.series("order") else { | ||
return n; | ||
}; | ||
|
||
let dimensionality = order.len(); | ||
|
||
let mut pos = [0_usize, 1, 2, 3]; | ||
let mut sgn = [1., 1., 1., 1.]; | ||
for (index, value) in order.iter().enumerate() { | ||
pos[index] = (value.abs() - 1.) as usize; | ||
sgn[index] = 1_f64.copysign(*value); | ||
} | ||
|
||
let mut successes = 0_usize; | ||
for i in 0..n { | ||
let inp = operands.get_coord(i); | ||
let mut out = inp; | ||
for index in 0..dimensionality { | ||
out[index] = inp[pos[index]] * sgn[index]; | ||
} | ||
operands.set_coord(i, &out); | ||
successes += 1; | ||
} | ||
|
||
successes | ||
} | ||
|
||
// ----- I N V E R S E ----------------------------------------------------------------- | ||
|
||
fn inv(op: &Op, _ctx: &dyn Context, operands: &mut dyn CoordinateSet) -> usize { | ||
let n = operands.len(); | ||
|
||
// We default to order=1,2,3,4, so if order is not given, we are done already | ||
let Ok(order) = op.params.series("order") else { | ||
return n; | ||
}; | ||
|
||
let dimensionality = order.len(); | ||
|
||
let mut pos = [0_usize, 1, 2, 3]; | ||
let mut sgn = [1., 1., 1., 1.]; | ||
for (index, value) in order.iter().enumerate() { | ||
pos[index] = (value.abs() - 1.) as usize; | ||
sgn[index] = 1_f64.copysign(*value); | ||
} | ||
|
||
let mut successes = 0_usize; | ||
for i in 0..n { | ||
let inp = operands.get_coord(i); | ||
let mut out = inp; | ||
for index in 0..dimensionality { | ||
out[pos[index]] = inp[index] * sgn[index]; | ||
} | ||
operands.set_coord(i, &out); | ||
successes += 1; | ||
} | ||
|
||
successes | ||
} | ||
|
||
// ----- C O N S T R U C T O R --------------------------------------------------------- | ||
|
||
// Example... | ||
#[rustfmt::skip] | ||
pub const GAMUT: [OpParameter; 2] = [ | ||
OpParameter::Flag { key: "inv" }, | ||
OpParameter::Series { key: "order", default: Some("1,2,3,4") }, | ||
]; | ||
|
||
pub fn new(parameters: &RawParameters, ctx: &dyn Context) -> Result<Op, Error> { | ||
let op = Op::plain(parameters, InnerOp(fwd), Some(InnerOp(inv)), &GAMUT, ctx)?; | ||
|
||
// We default to order=1,2,3,4, so if order is not given, all is OK | ||
let Ok(order) = op.params.series("order") else { | ||
return Ok(op); | ||
}; | ||
|
||
if order.len() > 4 { | ||
return Err(Error::BadParam( | ||
"order".to_string(), | ||
"More than 4 indices given".to_string(), | ||
)); | ||
} | ||
|
||
// While the Series type returns a Vec<f64>, the elements must be convertible to i64 | ||
// and further to (x.abs() as usize) for use as array indices | ||
for &o in order { | ||
let i = o as i64; | ||
if (i as f64) != o || i == 0 || (i.unsigned_abs() as usize) > order.len() { | ||
return Err(Error::BadParam("order".to_string(), o.to_string())); | ||
} | ||
} | ||
|
||
// PROJ does not allow duplicate axes, presumably for a well considered reason, | ||
// so neither do we | ||
for o in 1_u64..5 { | ||
if order.iter().filter(|x| (x.abs() as u64) == o).count() > 1 { | ||
return Err(Error::BadParam( | ||
"order".to_string(), | ||
"duplicate axis specified".to_string(), | ||
)); | ||
} | ||
} | ||
|
||
Ok(op) | ||
} | ||
|
||
// ----- T E S T S --------------------------------------------------------------------- | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use super::*; | ||
|
||
#[test] | ||
fn four_dim() -> Result<(), Error> { | ||
let mut ctx = Minimal::default(); | ||
let op = ctx.op("axisswap order=2,1,-3,-4")?; | ||
|
||
let mut operands = [Coor4D([1., 2., 3., 4.])]; | ||
|
||
// Forward | ||
ctx.apply(op, Fwd, &mut operands)?; | ||
assert_eq!(operands[0][0], 2.); | ||
assert_eq!(operands[0][1], 1.); | ||
assert_eq!(operands[0][2], -3.); | ||
assert_eq!(operands[0][3], -4.); | ||
|
||
// Inverse + roundtrip | ||
ctx.apply(op, Inv, &mut operands)?; | ||
assert_eq!(operands[0][0], 1.); | ||
assert_eq!(operands[0][1], 2.); | ||
assert_eq!(operands[0][2], 3.); | ||
assert_eq!(operands[0][3], 4.); | ||
|
||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn default_order() -> Result<(), Error> { | ||
let mut ctx = Minimal::default(); | ||
let op = ctx.op("axisswap")?; | ||
|
||
let mut operands = [Coor4D([1., 2., 3., 4.])]; | ||
|
||
// Forward | ||
ctx.apply(op, Fwd, &mut operands)?; | ||
assert_eq!(operands[0][0], 1.); | ||
assert_eq!(operands[0][1], 2.); | ||
assert_eq!(operands[0][2], 3.); | ||
assert_eq!(operands[0][3], 4.); | ||
|
||
// Inverse + roundtrip | ||
ctx.apply(op, Inv, &mut operands)?; | ||
assert_eq!(operands[0][0], 1.); | ||
assert_eq!(operands[0][1], 2.); | ||
assert_eq!(operands[0][2], 3.); | ||
assert_eq!(operands[0][3], 4.); | ||
|
||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn two_dim() -> Result<(), Error> { | ||
let mut ctx = Minimal::default(); | ||
|
||
let op = ctx.op("axisswap order=2,-1")?; | ||
let mut operands = [Coor4D([1., 2., 3., 4.])]; | ||
|
||
// Forward | ||
ctx.apply(op, Fwd, &mut operands)?; | ||
assert_eq!(operands[0][0], 2.); | ||
assert_eq!(operands[0][1], -1.); | ||
assert_eq!(operands[0][2], 3.); | ||
assert_eq!(operands[0][3], 4.); | ||
|
||
// Inverse + roundtrip | ||
ctx.apply(op, Inv, &mut operands)?; | ||
assert_eq!(operands[0][0], 1.); | ||
assert_eq!(operands[0][1], 2.); | ||
assert_eq!(operands[0][2], 3.); | ||
assert_eq!(operands[0][3], 4.); | ||
Ok(()) | ||
} | ||
|
||
#[test] | ||
fn bad_parameters() -> Result<(), Error> { | ||
let mut ctx = Minimal::default(); | ||
|
||
// Too many indices | ||
let op = ctx.op("axisswap order=4,4,4,2,-1"); | ||
assert!(matches!(op, Err(Error::BadParam(_, _)))); | ||
|
||
// Repeated indices | ||
let op = ctx.op("axisswap order=4,-4,2,-1"); | ||
assert!(matches!(op, Err(Error::BadParam(_, _)))); | ||
|
||
// Index exceeding dimensionality | ||
let op = ctx.op("axisswap order=2,3"); | ||
assert!(matches!(op, Err(Error::BadParam(_, _)))); | ||
|
||
// Missing indices ('order' becomes a flag) | ||
let op = ctx.op("axisswap order"); | ||
assert!(matches!(op, Err(Error::BadParam(_, _)))); | ||
|
||
// Missing all args: axisswap succeeds and becomes a no-op | ||
let op = ctx.op("axisswap"); | ||
assert!(op.is_ok()); | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters