unitforge 0.4.3__tar.gz → 0.5.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (94) hide show
  1. {unitforge-0.4.3 → unitforge-0.5.1}/Cargo.lock +6 -6
  2. {unitforge-0.4.3 → unitforge-0.5.1}/Cargo.toml +2 -9
  3. {unitforge-0.4.3 → unitforge-0.5.1}/PKG-INFO +1 -1
  4. {unitforge-0.4.3 → unitforge-0.5.1}/scripts/generate_python_stubs.py +4 -0
  5. {unitforge-0.4.3 → unitforge-0.5.1}/src/impl_macros.rs +22 -11
  6. {unitforge-0.4.3 → unitforge-0.5.1}/src/lib.rs +1 -32
  7. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities.template.rs +24 -6
  8. {unitforge-0.4.3 → unitforge-0.5.1}/src/small_linalg/bindings/vector3_py.template.rs +59 -13
  9. unitforge-0.5.1/tests/test_quantities.py +86 -0
  10. {unitforge-0.4.3 → unitforge-0.5.1}/tests/test_small_linalg.py +55 -2
  11. {unitforge-0.4.3 → unitforge-0.5.1}/unitforge_macros/Cargo.toml +2 -2
  12. unitforge-0.4.3/tests/test_quantities.py +0 -38
  13. {unitforge-0.4.3 → unitforge-0.5.1}/.gitignore +0 -0
  14. {unitforge-0.4.3 → unitforge-0.5.1}/.gitlab-ci.yml +0 -0
  15. {unitforge-0.4.3 → unitforge-0.5.1}/Readme.md +0 -0
  16. {unitforge-0.4.3 → unitforge-0.5.1}/Readme_py.md +0 -0
  17. {unitforge-0.4.3 → unitforge-0.5.1}/install_hooks.sh +0 -0
  18. {unitforge-0.4.3 → unitforge-0.5.1}/pyproject.toml +0 -0
  19. {unitforge-0.4.3 → unitforge-0.5.1}/python/unitforge/__init__.py +0 -0
  20. {unitforge-0.4.3 → unitforge-0.5.1}/src/errors.rs +0 -0
  21. {unitforge-0.4.3 → unitforge-0.5.1}/src/internal.rs +0 -0
  22. {unitforge-0.4.3 → unitforge-0.5.1}/src/prelude.rs +0 -0
  23. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/acceleration.rs +0 -0
  24. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/angle.rs +0 -0
  25. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/angular_acceleration.rs +0 -0
  26. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/angular_velocity.rs +0 -0
  27. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/area.rs +0 -0
  28. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/area_of_moment.rs +0 -0
  29. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/charge.rs +0 -0
  30. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/compliance.rs +0 -0
  31. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/density.rs +0 -0
  32. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/distance.rs +0 -0
  33. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/force.rs +0 -0
  34. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/force_area.rs +0 -0
  35. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/force_distance.rs +0 -0
  36. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/force_div_distance_power_four.rs +0 -0
  37. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/force_per_volume.rs +0 -0
  38. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/force_stress.rs +0 -0
  39. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/force_volume.rs +0 -0
  40. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/frequency.rs +0 -0
  41. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/inverse_angle.rs +0 -0
  42. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/inverse_area.rs +0 -0
  43. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/inverse_distance.rs +0 -0
  44. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/inverse_stress.rs +0 -0
  45. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/mass.rs +0 -0
  46. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/mass_per_distance.rs +0 -0
  47. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/molar_mass.rs +0 -0
  48. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/pressure_rate.rs +0 -0
  49. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/rotational_stiffness.rs +0 -0
  50. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/specific_gas_constant.rs +0 -0
  51. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/stiffness.rs +0 -0
  52. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/strain.rs +0 -0
  53. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/stress.rs +0 -0
  54. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/stress_squared.rs +0 -0
  55. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/temperature.rs +0 -0
  56. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/time.rs +0 -0
  57. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/universal_gas_constant.rs +0 -0
  58. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/velocity.rs +0 -0
  59. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/velocity_squared.rs +0 -0
  60. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/voltage.rs +0 -0
  61. {unitforge-0.4.3 → unitforge-0.5.1}/src/quantities/volume.rs +0 -0
  62. {unitforge-0.4.3 → unitforge-0.5.1}/src/relations.rs +0 -0
  63. {unitforge-0.4.3 → unitforge-0.5.1}/src/small_linalg/bindings/matrix3_py.template.rs +0 -0
  64. {unitforge-0.4.3 → unitforge-0.5.1}/src/small_linalg/matrix2.rs +0 -0
  65. {unitforge-0.4.3 → unitforge-0.5.1}/src/small_linalg/matrix2x3.rs +0 -0
  66. {unitforge-0.4.3 → unitforge-0.5.1}/src/small_linalg/matrix3.rs +0 -0
  67. {unitforge-0.4.3 → unitforge-0.5.1}/src/small_linalg/matrix3x2.rs +0 -0
  68. {unitforge-0.4.3 → unitforge-0.5.1}/src/small_linalg/vector2.rs +0 -0
  69. {unitforge-0.4.3 → unitforge-0.5.1}/src/small_linalg/vector3.rs +0 -0
  70. {unitforge-0.4.3 → unitforge-0.5.1}/test.sh +0 -0
  71. {unitforge-0.4.3 → unitforge-0.5.1}/tests/common/mod.rs +0 -0
  72. {unitforge-0.4.3 → unitforge-0.5.1}/tests/matrix2.rs +0 -0
  73. {unitforge-0.4.3 → unitforge-0.5.1}/tests/matrix2x3.rs +0 -0
  74. {unitforge-0.4.3 → unitforge-0.5.1}/tests/matrix3.rs +0 -0
  75. {unitforge-0.4.3 → unitforge-0.5.1}/tests/matrix3x2.rs +0 -0
  76. {unitforge-0.4.3 → unitforge-0.5.1}/tests/ndarray_dot.rs +0 -0
  77. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantities.rs +0 -0
  78. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core/constants.rs +0 -0
  79. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core/construction.rs +0 -0
  80. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core/enum_iter.rs +0 -0
  81. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core/metadata.rs +0 -0
  82. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core/normalization.rs +0 -0
  83. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core/plain_storage.rs +0 -0
  84. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core/relations.rs +0 -0
  85. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core/runtime_quantity.rs +0 -0
  86. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core/scalar_edges.rs +0 -0
  87. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_core.rs +0 -0
  88. {unitforge-0.4.3 → unitforge-0.5.1}/tests/quantity_serde.rs +0 -0
  89. {unitforge-0.4.3 → unitforge-0.5.1}/tests/small_linalg_edge_cases.rs +0 -0
  90. {unitforge-0.4.3 → unitforge-0.5.1}/tests/small_linalg_interfaces.rs +0 -0
  91. {unitforge-0.4.3 → unitforge-0.5.1}/tests/typecheck/mypy_smoke.py +0 -0
  92. {unitforge-0.4.3 → unitforge-0.5.1}/tests/vector2.rs +0 -0
  93. {unitforge-0.4.3 → unitforge-0.5.1}/tests/vector3.rs +0 -0
  94. {unitforge-0.4.3 → unitforge-0.5.1}/unitforge_macros/src/lib.rs +0 -0
@@ -231,9 +231,9 @@ checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
231
231
 
232
232
  [[package]]
233
233
  name = "regex"
234
- version = "1.11.1"
234
+ version = "1.12.3"
235
235
  source = "registry+https://github.com/rust-lang/crates.io-index"
236
- checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191"
236
+ checksum = "e10754a14b9137dd7b1e3e5b0493cc9171fdd105e0ab477f51b72e7f3ac0e276"
237
237
  dependencies = [
238
238
  "aho-corasick",
239
239
  "memchr",
@@ -243,9 +243,9 @@ dependencies = [
243
243
 
244
244
  [[package]]
245
245
  name = "regex-automata"
246
- version = "0.4.9"
246
+ version = "0.4.14"
247
247
  source = "registry+https://github.com/rust-lang/crates.io-index"
248
- checksum = "809e8dc61f6de73b46c85f4c96486310fe304c434cfa43669d7b40f711150908"
248
+ checksum = "6e1dd4122fc1595e8162618945476892eefca7b88c52820e74af6262213cae8f"
249
249
  dependencies = [
250
250
  "aho-corasick",
251
251
  "memchr",
@@ -351,7 +351,7 @@ checksum = "5a5f39404a5da50712a4c1eecf25e90dd62b613502b7e925fd4e4d19b5c96512"
351
351
 
352
352
  [[package]]
353
353
  name = "unitforge"
354
- version = "0.4.3"
354
+ version = "0.5.1"
355
355
  dependencies = [
356
356
  "ndarray",
357
357
  "num-traits",
@@ -367,7 +367,7 @@ dependencies = [
367
367
 
368
368
  [[package]]
369
369
  name = "unitforge_macros"
370
- version = "0.4.3"
370
+ version = "0.5.1"
371
371
  dependencies = [
372
372
  "regex",
373
373
  ]
@@ -1,10 +1,6 @@
1
- [lib]
2
- name = "unitforge"
3
- crate-type = ["cdylib", "rlib"]
4
-
5
1
  [package]
6
2
  name = "unitforge"
7
- version = "0.4.3"
3
+ version = "0.5.1"
8
4
  edition = "2021"
9
5
  authors = ["Dr.Q team <info@askdrq.com>", "Henrik Stromberg <henrik@askdrq.com>"]
10
6
  description = "A library for unit and quantity consistent computations in Rust"
@@ -24,7 +20,7 @@ pyo3 = { version = "0.28.3", features = ["extension-module", "auto-initialize",
24
20
  numpy = { version = "0.28.0", optional = true }
25
21
  strum = { version = "0.28.0", optional = true }
26
22
  strum_macros = { version = "0.28.0", optional = true }
27
- unitforge_macros = { version = "0.4.3", path = "unitforge_macros" }
23
+ unitforge_macros = { version = "0.5.1", path = "unitforge_macros" }
28
24
 
29
25
  [features]
30
26
  default = []
@@ -38,6 +34,3 @@ no_std = []
38
34
 
39
35
  [workspace]
40
36
  members = ["unitforge_macros"]
41
-
42
- [profile.dev]
43
- panic = "abort"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: unitforge
3
- Version: 0.4.3
3
+ Version: 0.5.1
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -274,7 +274,9 @@ def render_vector3_stub() -> List[str]:
274
274
  ' def dot_vec(self, other: "Vector3") -> Any: ...',
275
275
  ' def cross(self, other: "Vector3") -> "Vector3": ...',
276
276
  " def __mul__(self, rhs: Any) -> Vector3: ...",
277
+ " def __imul__(self, rhs: Any) -> None: ...",
277
278
  " def __truediv__(self, rhs: Any) -> Vector3: ...",
279
+ " def __itruediv__(self, rhs: Any) -> None: ...",
278
280
  ]
279
281
 
280
282
 
@@ -341,6 +343,8 @@ def render_quantity_stub(
341
343
  f' def __sub__(self, rhs: "{q}") -> "{q}": ...',
342
344
  " @overload",
343
345
  f' def __mul__(self, rhs: float) -> "{q}": ...',
346
+ " @overload",
347
+ " def __mul__(self, rhs: Vector3) -> Vector3: ...",
344
348
  ]
345
349
 
346
350
  mul_ops = operator_maps.mul.get(q, {})
@@ -16,8 +16,11 @@ macro_rules! impl_quantity {
16
16
  use pyo3::pyclass;
17
17
  #[cfg(feature = "pyo3")]
18
18
  use pyo3::{
19
- basic::CompareOp, exceptions::PyValueError, pymethods, types::PyType, Bound,
20
- IntoPyObject, Py, PyAny, PyRef, PyResult,
19
+ basic::CompareOp,
20
+ exceptions::PyTypeError,
21
+ pymethods,
22
+ types::{PyAnyMethods, PyType},
23
+ Bound, IntoPyObject, Py, PyAny, PyRef, PyResult,
21
24
  };
22
25
 
23
26
  #[cfg(feature = "strum")]
@@ -395,13 +398,21 @@ macro_rules! impl_quantity {
395
398
  let py = lhs.py();
396
399
  let rhs_ref = rhs.bind(py);
397
400
  let lhs_ref = lhs.into_pyobject(py)?;
398
- let rhs_quantity = Quantity::from_py_any(rhs_ref)
399
- .map_err(|e| PyValueError::new_err(e.to_string()))?;
400
401
  let lhs_quantity = Quantity::from_py_any(&lhs_ref)
401
- .map_err(|e| PyValueError::new_err(e.to_string()))?;
402
+ .map_err(|e| PyTypeError::new_err(e.to_string()))?;
403
+ if let Ok(rhs_vector) = rhs_ref.extract::<$crate::Vector3Py>() {
404
+ return Ok(rhs_vector
405
+ .mul_quantity_left(lhs_quantity)?
406
+ .into_pyobject(py)?
407
+ .into_any()
408
+ .unbind());
409
+ }
410
+
411
+ let rhs_quantity = Quantity::from_py_any(rhs_ref)
412
+ .map_err(|e| PyTypeError::new_err(e.to_string()))?;
402
413
  match lhs_quantity * rhs_quantity {
403
414
  Ok(value) => Ok(value.to_pyobject(py)?),
404
- Err(_) => Err(PyValueError::new_err(
415
+ Err(_) => Err(PyTypeError::new_err(
405
416
  "Multiplication of given objects is not possible.",
406
417
  )),
407
418
  }
@@ -412,12 +423,12 @@ macro_rules! impl_quantity {
412
423
  let rhs_ref = rhs.bind(py);
413
424
  let lhs_ref = lhs.into_pyobject(py)?;
414
425
  let rhs_quantity = Quantity::from_py_any(rhs_ref)
415
- .map_err(|e| PyValueError::new_err(e.to_string()))?;
426
+ .map_err(|e| PyTypeError::new_err(e.to_string()))?;
416
427
  let lhs_quantity = Quantity::from_py_any(&lhs_ref)
417
- .map_err(|e| PyValueError::new_err(e.to_string()))?;
428
+ .map_err(|e| PyTypeError::new_err(e.to_string()))?;
418
429
  match lhs_quantity / rhs_quantity {
419
430
  Ok(value) => Ok(value.to_pyobject(py)?),
420
- Err(_) => Err(PyValueError::new_err(
431
+ Err(_) => Err(PyTypeError::new_err(
421
432
  "Division of given objects is not possible.",
422
433
  )),
423
434
  }
@@ -435,10 +446,10 @@ macro_rules! impl_quantity {
435
446
  let py = lhs.py();
436
447
  let lhs_ref = lhs.into_pyobject(py)?;
437
448
  let lhs_quantity = Quantity::from_py_any(&lhs_ref)
438
- .map_err(|e| PyValueError::new_err(e.to_string()))?;
449
+ .map_err(|e| PyTypeError::new_err(e.to_string()))?;
439
450
  match lhs_quantity.powi(rhs) {
440
451
  Ok(value) => Ok(value.to_pyobject(py)?),
441
- Err(_) => Err(PyValueError::new_err(
452
+ Err(_) => Err(PyTypeError::new_err(
442
453
  "Power of given object is not possible.",
443
454
  )),
444
455
  }
@@ -1,37 +1,6 @@
1
- #![cfg_attr(
2
- all(
3
- feature = "no_std",
4
- any(feature = "storage-f64", feature = "storage-f32"),
5
- not(all(feature = "storage-f64", feature = "storage-f32")),
6
- not(any(
7
- feature = "serde",
8
- feature = "ndarray",
9
- feature = "pyo3",
10
- feature = "strum"
11
- ))
12
- ),
13
- no_std
14
- )]
1
+ #![cfg_attr(feature = "no_std", no_std)]
15
2
  #![allow(non_camel_case_types)]
16
3
 
17
- #[cfg(all(
18
- feature = "no_std",
19
- any(feature = "storage-f64", feature = "storage-f32"),
20
- not(all(feature = "storage-f64", feature = "storage-f32")),
21
- not(any(
22
- feature = "serde",
23
- feature = "ndarray",
24
- feature = "pyo3",
25
- feature = "strum"
26
- )),
27
- not(test),
28
- not(target_os = "none")
29
- ))]
30
- #[panic_handler]
31
- fn panic(_info: &core::panic::PanicInfo<'_>) -> ! {
32
- loop {}
33
- }
34
-
35
4
  #[cfg(all(feature = "storage-f64", feature = "storage-f32"))]
36
5
  compile_error!("features `storage-f64` and `storage-f32` are mutually exclusive");
37
6
 
@@ -207,10 +207,9 @@ fn extract_f64(v: &Bound<PyAny>) -> Option<f64> {
207
207
  #[cfg(feature = "pyo3")]
208
208
  impl Quantity {
209
209
  pub fn from_py_any(v: &Bound<PyAny>) -> crate::QuantityConversionResult<Self> {
210
- if let Some(inner) = extract_f64(v) {
211
- Ok(Quantity::FloatQuantity(inner))
210
+ if let Ok(inner) = Self::from_py_any_direct(v) {
211
+ Ok(inner)
212
212
  }
213
- // __EXTRACT_QUANTITY_MATCHES__
214
213
  else if let Ok(inner) = pyo3_capsule_api::extract_quantity_from_capsule(v) {
215
214
  Ok(inner)
216
215
  }
@@ -219,6 +218,16 @@ impl Quantity {
219
218
  }
220
219
  }
221
220
 
221
+ pub(crate) fn from_py_any_direct(v: &Bound<PyAny>) -> crate::QuantityConversionResult<Self> {
222
+ if let Some(inner) = extract_f64(v) {
223
+ Ok(Quantity::FloatQuantity(inner))
224
+ }
225
+ // __EXTRACT_QUANTITY_MATCHES__
226
+ else {
227
+ Err(crate::QuantityConversionError::InvalidPythonQuantity)
228
+ }
229
+ }
230
+
222
231
  pub fn to_pyobject(self, py: Python) -> PyResult<Py<PyAny>> {
223
232
  Ok(match self {
224
233
  Quantity::FloatQuantity(v) => v.into_pyobject(py).map(|obj| obj.into())?,
@@ -240,7 +249,9 @@ impl Display for Quantity {
240
249
  impl Unit {
241
250
 
242
251
  pub fn from_py_any(v: &Bound<PyAny>) -> crate::QuantityConversionResult<Self> {
243
- // __EXTRACT_UNIT_MATCHES__
252
+ if let Ok(inner) = Self::from_py_any_direct(v) {
253
+ Ok(inner)
254
+ }
244
255
  else if let Ok(inner) = pyo3_capsule_api::extract_unit_from_capsule(v) {
245
256
  Ok(inner)
246
257
  }
@@ -248,6 +259,13 @@ impl Unit {
248
259
  Err(crate::QuantityConversionError::InvalidPythonUnit)
249
260
  }
250
261
  }
262
+
263
+ pub(crate) fn from_py_any_direct(v: &Bound<PyAny>) -> crate::QuantityConversionResult<Self> {
264
+ // __EXTRACT_UNIT_MATCHES__
265
+ else {
266
+ Err(crate::QuantityConversionError::InvalidPythonUnit)
267
+ }
268
+ }
251
269
  }
252
270
 
253
271
  #[cfg(feature = "pyo3")]
@@ -400,7 +418,7 @@ pub mod pyo3_capsule_api {
400
418
  return Err(PyValueError::new_err("output pointer for quantity repr is null"));
401
419
  }
402
420
  let obj_bound = Bound::from_borrowed_ptr(py, obj);
403
- let quantity = Quantity::from_py_any(&obj_bound)
421
+ let quantity = Quantity::from_py_any_direct(&obj_bound)
404
422
  .map_err(|err| PyValueError::new_err(err.to_string()))?;
405
423
  unsafe {
406
424
  *out = quantity_to_repr(quantity)
@@ -444,7 +462,7 @@ pub mod pyo3_capsule_api {
444
462
  return Err(PyValueError::new_err("output pointer for unit repr is null"));
445
463
  }
446
464
  let obj_bound = Bound::from_borrowed_ptr(py, obj);
447
- let unit = Unit::from_py_any(&obj_bound)
465
+ let unit = Unit::from_py_any_direct(&obj_bound)
448
466
  .map_err(|err| PyValueError::new_err(err.to_string()))?;
449
467
  unsafe {
450
468
  *out = unit_to_repr(unit);
@@ -1,5 +1,5 @@
1
1
  use std::mem::discriminant;
2
- use pyo3::{exceptions::PyValueError, types::PyList};
2
+ use pyo3::{exceptions::{PyTypeError, PyValueError}, types::PyList};
3
3
  use numpy::{PyReadonlyArray1, PyArray1};
4
4
  use crate::small_linalg::Vector3;
5
5
 
@@ -38,6 +38,36 @@ impl Vector3Py {
38
38
  self.data[2]
39
39
  )
40
40
  }
41
+
42
+ fn mul_quantity_right_data(&self, rhs: Quantity) -> PyResult<[Quantity; 3]> {
43
+ Ok([
44
+ (self.data[0] * rhs).map_err(|_| PyTypeError::new_err("Cannot multiply given Quantities."))?,
45
+ (self.data[1] * rhs).map_err(|_| PyTypeError::new_err("Cannot multiply given Quantities."))?,
46
+ (self.data[2] * rhs).map_err(|_| PyTypeError::new_err("Cannot multiply given Quantities."))?,
47
+ ])
48
+ }
49
+
50
+ fn mul_quantity_left_data(&self, lhs: Quantity) -> PyResult<[Quantity; 3]> {
51
+ Ok([
52
+ (lhs * self.data[0]).map_err(|_| PyTypeError::new_err("Cannot multiply given Quantities."))?,
53
+ (lhs * self.data[1]).map_err(|_| PyTypeError::new_err("Cannot multiply given Quantities."))?,
54
+ (lhs * self.data[2]).map_err(|_| PyTypeError::new_err("Cannot multiply given Quantities."))?,
55
+ ])
56
+ }
57
+
58
+ fn div_quantity_right_data(&self, rhs: Quantity) -> PyResult<[Quantity; 3]> {
59
+ Ok([
60
+ (self.data[0] / rhs).map_err(|_| PyTypeError::new_err("Cannot divide given Quantities."))?,
61
+ (self.data[1] / rhs).map_err(|_| PyTypeError::new_err("Cannot divide given Quantities."))?,
62
+ (self.data[2] / rhs).map_err(|_| PyTypeError::new_err("Cannot divide given Quantities."))?,
63
+ ])
64
+ }
65
+
66
+ pub fn mul_quantity_left(&self, lhs: Quantity) -> PyResult<Self> {
67
+ Ok(Vector3Py {
68
+ data: self.mul_quantity_left_data(lhs)?,
69
+ })
70
+ }
41
71
  }
42
72
 
43
73
  #[pymethods]
@@ -127,7 +157,7 @@ impl Vector3Py {
127
157
 
128
158
  fn __add__(&self, other: &Vector3Py) -> PyResult<Self> {
129
159
  if discriminant(&self.data[0]) != discriminant(&other.data[0]) {
130
- Err(PyValueError::new_err("The passed values must be of the same quantity.".to_string()))
160
+ Err(PyTypeError::new_err("The passed values must be of the same quantity.".to_string()))
131
161
  } else {
132
162
  Ok(Vector3Py {
133
163
  data: [self.data[0].add(other.data[0]).unwrap(),
@@ -139,7 +169,7 @@ impl Vector3Py {
139
169
 
140
170
  fn __sub__(&self, other: &Vector3Py) -> PyResult<Self> {
141
171
  if discriminant(&self.data[0]) != discriminant(&other.data[0]) {
142
- Err(PyValueError::new_err("The passed values must be of the same quantity.".to_string()))
172
+ Err(PyTypeError::new_err("The passed values must be of the same quantity.".to_string()))
143
173
  } else {
144
174
  Ok(Vector3Py {
145
175
  data: [self.data[0].sub(other.data[0]).unwrap(),
@@ -338,20 +368,36 @@ impl Vector3Py {
338
368
  fn __mul__(&self, py: Python, rhs: Py<PyAny>) -> PyResult<Self> {
339
369
  let rhs_ref = rhs.bind(py);
340
370
  let rhs_quantity = Quantity::from_py_any(rhs_ref)
341
- .map_err(|e| PyValueError::new_err(e.to_string()))?;
342
- Ok(Vector3Py { data: [
343
- (self.data[0] * rhs_quantity).map_err(|_| PyValueError::new_err("Cannot multiply given Quantities."))?,
344
- (self.data[1] * rhs_quantity).map_err(|_| PyValueError::new_err("Cannot multiply given Quantities."))?,
345
- (self.data[2] * rhs_quantity).map_err(|_| PyValueError::new_err("Cannot multiply given Quantities."))?] })
371
+ .map_err(|e| PyTypeError::new_err(e.to_string()))?;
372
+ Ok(Vector3Py {
373
+ data: self.mul_quantity_right_data(rhs_quantity)?,
374
+ })
346
375
  }
347
376
 
348
377
  fn __truediv__(&self, py: Python, rhs: Py<PyAny>) -> PyResult<Self> {
349
378
  let rhs_ref = rhs.bind(py);
350
379
  let rhs_quantity = Quantity::from_py_any(rhs_ref)
351
- .map_err(|e| PyValueError::new_err(e.to_string()))?;
352
- Ok(Vector3Py { data: [
353
- (self.data[0] / rhs_quantity).map_err(|_| PyValueError::new_err("Cannot divide given Quantities."))?,
354
- (self.data[1] / rhs_quantity).map_err(|_| PyValueError::new_err("Cannot divide given Quantities."))?,
355
- (self.data[2] / rhs_quantity).map_err(|_| PyValueError::new_err("Cannot divide given Quantities."))?] })
380
+ .map_err(|e| PyTypeError::new_err(e.to_string()))?;
381
+ Ok(Vector3Py {
382
+ data: self.div_quantity_right_data(rhs_quantity)?,
383
+ })
384
+ }
385
+
386
+ fn __imul__(&mut self, py: Python, rhs: Py<PyAny>) -> PyResult<()> {
387
+ let rhs_ref = rhs.bind(py);
388
+ let rhs_quantity = Quantity::from_py_any(rhs_ref)
389
+ .map_err(|e| PyTypeError::new_err(e.to_string()))?;
390
+ let data = self.mul_quantity_right_data(rhs_quantity)?;
391
+ self.data = data;
392
+ Ok(())
393
+ }
394
+
395
+ fn __itruediv__(&mut self, py: Python, rhs: Py<PyAny>) -> PyResult<()> {
396
+ let rhs_ref = rhs.bind(py);
397
+ let rhs_quantity = Quantity::from_py_any(rhs_ref)
398
+ .map_err(|e| PyTypeError::new_err(e.to_string()))?;
399
+ let data = self.div_quantity_right_data(rhs_quantity)?;
400
+ self.data = data;
401
+ Ok(())
356
402
  }
357
403
  }
@@ -0,0 +1,86 @@
1
+ import pytest
2
+ from unitforge import *
3
+
4
+
5
+ def test_inverse_stress_from_stress_division():
6
+ stress = Stress(2.0, StressUnit.Pa)
7
+ inverse = 1 / stress
8
+ assert isinstance(inverse, InverseStress)
9
+ assert inverse.to(InverseStressUnit._Pa) == pytest.approx(0.5)
10
+
11
+
12
+ def test_stress_from_inverse_stress_division():
13
+ inverse = InverseStress(0.5, InverseStressUnit._Pa)
14
+ stress = 1 / inverse
15
+ assert isinstance(stress, Stress)
16
+ assert stress.to(StressUnit.Pa) == pytest.approx(2.0)
17
+
18
+
19
+ def test_stress_squared_from_force_stress_over_area():
20
+ force = Force(2.0, ForceUnit.N)
21
+ stress = Stress(3.0, StressUnit.Pa)
22
+ area = Area(6.0, AreaUnit.msq)
23
+ result = (force * stress) / area
24
+ assert isinstance(result, StressSquared)
25
+ assert result.to(StressSquaredUnit.Nsq_mmhc) == pytest.approx(1.0)
26
+
27
+
28
+ def test_force_stress_from_stress_squared_times_area():
29
+ stress_squared = StressSquared(2.0, StressSquaredUnit.Nsq_mmhc)
30
+ area = Area(3.0, AreaUnit.msq)
31
+ result = stress_squared * area
32
+ assert isinstance(result, ForceStress)
33
+ assert result.to(ForceStressUnit.Nsq_msq) == pytest.approx(6.0)
34
+
35
+ def test_power_operator():
36
+ a = Distance(2., DistanceUnit.mm)
37
+ b = a**2
38
+ assert b == Area(4., AreaUnit.mmsq)
39
+
40
+
41
+ def test_quantity_unsupported_rhs_object_raises_type_error():
42
+ force = Force(1., ForceUnit.N)
43
+ with pytest.raises(TypeError):
44
+ _ = force + object()
45
+ with pytest.raises(TypeError):
46
+ _ = force - object()
47
+ with pytest.raises(TypeError):
48
+ _ = force * object()
49
+ with pytest.raises(TypeError):
50
+ _ = force / object()
51
+
52
+
53
+ def test_quantity_unsupported_relation_raises_type_error():
54
+ force = Force(1., ForceUnit.N)
55
+ time = Time(1., TimeUnit.s)
56
+ with pytest.raises(TypeError):
57
+ _ = force * time
58
+ with pytest.raises(TypeError):
59
+ _ = force / time
60
+
61
+
62
+ def test_quantity_invalid_power_raises_type_error():
63
+ force = Force(1., ForceUnit.N)
64
+ with pytest.raises(TypeError):
65
+ _ = force ** 2
66
+
67
+
68
+ def test_quantity_left_vector_multiplication():
69
+ result = Force(1., ForceUnit.N) * Vector3.x()
70
+ assert result[0] == Force(1., ForceUnit.N)
71
+ assert result[1] == Force(0., ForceUnit.N)
72
+ assert result[2] == Force(0., ForceUnit.N)
73
+
74
+
75
+ def test_quantity_left_vector_multiplication_with_quantity_vector():
76
+ vec = Vector3(Distance(3., DistanceUnit.mm), Distance(4., DistanceUnit.mm), Distance(12., DistanceUnit.mm))
77
+ result = Force(2., ForceUnit.N) * vec
78
+ assert result[0].to(ForceDistanceUnit.Nm) == 0.006
79
+ assert result[1].to(ForceDistanceUnit.Nm) == 0.008
80
+ assert result[2].to(ForceDistanceUnit.Nm) == 0.024
81
+
82
+
83
+ def test_quantity_left_vector_multiplication_unsupported_relation_raises_type_error():
84
+ vec = Vector3(Time(1., TimeUnit.s), Time(2., TimeUnit.s), Time(3., TimeUnit.s))
85
+ with pytest.raises(TypeError):
86
+ _ = Force(1., ForceUnit.N) * vec
@@ -53,7 +53,7 @@ class TestVectorAdd():
53
53
  assert c[2] == 8.
54
54
 
55
55
  def test_vector_add_failing(self):
56
- with pytest.raises(ValueError, match="The passed values must be of the same quantity."):
56
+ with pytest.raises(TypeError, match="The passed values must be of the same quantity."):
57
57
  a = Vector3(1., 12., 3.)
58
58
  b = Vector3(Distance(2., DistanceUnit.mm), Distance(2., DistanceUnit.mm), Distance(2., DistanceUnit.mm))
59
59
  _ = a + b
@@ -80,7 +80,7 @@ def test_vector_shared_unit():
80
80
  assert vec[2].to(ForceUnit.kN) == 3.
81
81
 
82
82
  def test_vector_sub_failing():
83
- with pytest.raises(ValueError, match="The passed values must be of the same quantity."):
83
+ with pytest.raises(TypeError, match="The passed values must be of the same quantity."):
84
84
  a = Vector3(1., 12., 3.)
85
85
  b = Vector3(Distance(2., DistanceUnit.mm), Distance(2., DistanceUnit.mm), Distance(2., DistanceUnit.mm))
86
86
  _ = a - b
@@ -185,6 +185,22 @@ def test_vector_mul():
185
185
  assert res[1].to(ForceDistanceUnit.Nm) == 0.008
186
186
  assert res[2].to(ForceDistanceUnit.Nm) == 0.024
187
187
 
188
+ def test_vector_float_mul_quantity():
189
+ res = Vector3.x() * Force(1., ForceUnit.N)
190
+ assert res[0] == Force(1., ForceUnit.N)
191
+ assert res[1] == Force(0., ForceUnit.N)
192
+ assert res[2] == Force(0., ForceUnit.N)
193
+
194
+ def test_vector_mul_unsupported_rhs_raises_type_error():
195
+ vec = Vector3(Distance(3., DistanceUnit.mm), Distance(4., DistanceUnit.mm), Distance(12., DistanceUnit.mm))
196
+ with pytest.raises(TypeError):
197
+ _ = vec * object()
198
+
199
+ def test_vector_mul_unsupported_quantity_relation_raises_type_error():
200
+ vec = Vector3(Force(3., ForceUnit.N), Force(4., ForceUnit.N), Force(12., ForceUnit.N))
201
+ with pytest.raises(TypeError):
202
+ _ = vec * Time(1., TimeUnit.s)
203
+
188
204
  def test_vector_div():
189
205
  vec = Vector3(Force(3., ForceUnit.N), Force(4., ForceUnit.N), Force(12., ForceUnit.N))
190
206
  res = vec / Area(2., AreaUnit.mmsq)
@@ -192,6 +208,36 @@ def test_vector_div():
192
208
  assert res[1].to(StressUnit.MPa) == 2.
193
209
  assert res[2].to(StressUnit.MPa) == 6.
194
210
 
211
+ def test_vector_div_unsupported_rhs_raises_type_error():
212
+ vec = Vector3(Force(3., ForceUnit.N), Force(4., ForceUnit.N), Force(12., ForceUnit.N))
213
+ with pytest.raises(TypeError):
214
+ _ = vec / object()
215
+
216
+ def test_vector_imul_quantity_mutates_in_place():
217
+ vec = Vector3.x()
218
+ original_id = id(vec)
219
+ vec *= Force(2., ForceUnit.N)
220
+ assert id(vec) == original_id
221
+ assert vec[0] == Force(2., ForceUnit.N)
222
+ assert vec[1] == Force(0., ForceUnit.N)
223
+ assert vec[2] == Force(0., ForceUnit.N)
224
+
225
+ def test_vector_itruediv_quantity_mutates_in_place():
226
+ vec = Vector3(Force(3., ForceUnit.N), Force(4., ForceUnit.N), Force(12., ForceUnit.N))
227
+ original_id = id(vec)
228
+ vec /= Area(2., AreaUnit.mmsq)
229
+ assert id(vec) == original_id
230
+ assert vec[0].to(StressUnit.MPa) == 1.5
231
+ assert vec[1].to(StressUnit.MPa) == 2.
232
+ assert vec[2].to(StressUnit.MPa) == 6.
233
+
234
+ def test_vector_imul_unsupported_quantity_leaves_vector_unchanged():
235
+ vec = Vector3(Force(3., ForceUnit.N), Force(4., ForceUnit.N), Force(12., ForceUnit.N))
236
+ before = vec.to_list()
237
+ with pytest.raises(TypeError):
238
+ vec *= Time(1., TimeUnit.s)
239
+ assert vec.to_list() == before
240
+
195
241
  def test_vector_matrix_zero():
196
242
  mat = Matrix3.zero()
197
243
  for i in range(3):
@@ -229,6 +275,13 @@ def test_matrix_str():
229
275
  mat = Matrix3.from_list([[1, 3, 4], [5, 7, 3], [7, 8, 9]], ForceUnit.N)
230
276
  assert str(mat) == 'Matrix3: [\n[ForceQuantity(1 N), ForceQuantity(3 N), ForceQuantity(4 N)]\n[ForceQuantity(5 N), ForceQuantity(7 N), ForceQuantity(3 N)]\n[ForceQuantity(7 N), ForceQuantity(8 N), ForceQuantity(9 N)]]'
231
277
 
278
+ def test_matrix_missing_binary_arithmetic_raises_type_error():
279
+ mat = Matrix3.zero()
280
+ with pytest.raises(TypeError):
281
+ _ = mat + mat
282
+ with pytest.raises(TypeError):
283
+ _ = mat * 2.
284
+
232
285
 
233
286
  class TestMatrixListInterface:
234
287
  def test_matrix_from_list(self):
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "unitforge_macros"
3
- version = "0.4.3"
3
+ version = "0.5.1"
4
4
  edition = "2021"
5
5
  authors = ["Dr.Q team <info@askdrq.com>", "Henrik Stromberg <henrik@askdrq.com>"]
6
6
  description = "Proc macros for generating units, quantities, and Python bindings in unitforge"
@@ -12,4 +12,4 @@ keywords = ["units", "quantities", "simulation", "physics"]
12
12
  proc-macro = true
13
13
 
14
14
  [dependencies]
15
- regex = "1.11.1"
15
+ regex = "1.12.3"
@@ -1,38 +0,0 @@
1
- import pytest
2
- from unitforge import *
3
-
4
-
5
- def test_inverse_stress_from_stress_division():
6
- stress = Stress(2.0, StressUnit.Pa)
7
- inverse = 1 / stress
8
- assert isinstance(inverse, InverseStress)
9
- assert inverse.to(InverseStressUnit._Pa) == pytest.approx(0.5)
10
-
11
-
12
- def test_stress_from_inverse_stress_division():
13
- inverse = InverseStress(0.5, InverseStressUnit._Pa)
14
- stress = 1 / inverse
15
- assert isinstance(stress, Stress)
16
- assert stress.to(StressUnit.Pa) == pytest.approx(2.0)
17
-
18
-
19
- def test_stress_squared_from_force_stress_over_area():
20
- force = Force(2.0, ForceUnit.N)
21
- stress = Stress(3.0, StressUnit.Pa)
22
- area = Area(6.0, AreaUnit.msq)
23
- result = (force * stress) / area
24
- assert isinstance(result, StressSquared)
25
- assert result.to(StressSquaredUnit.Nsq_mmhc) == pytest.approx(1.0)
26
-
27
-
28
- def test_force_stress_from_stress_squared_times_area():
29
- stress_squared = StressSquared(2.0, StressSquaredUnit.Nsq_mmhc)
30
- area = Area(3.0, AreaUnit.msq)
31
- result = stress_squared * area
32
- assert isinstance(result, ForceStress)
33
- assert result.to(ForceStressUnit.Nsq_msq) == pytest.approx(6.0)
34
-
35
- def test_power_operator():
36
- a = Distance(2., DistanceUnit.mm)
37
- b = a**2
38
- assert b == Area(4., AreaUnit.mmsq)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes