Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extended analysis #36

Closed
jedbrown opened this issue Dec 5, 2023 · 3 comments
Closed

Extended analysis #36

jedbrown opened this issue Dec 5, 2023 · 3 comments

Comments

@jedbrown
Copy link
Contributor

jedbrown commented Dec 5, 2023

I just wanted to mention some extensions in case a good implementation strategy for any emerge

  1. Orientational analysis provides a way to handle angles and could distinguish quantities like energy and torque, which naively have the same dimensions (e.g., newton meters).
  2. uom distinguishes (absolute) ThermodynamicTemperature from TemperatureInterval, and one can't add two absolute temperatures. There are other places where distinguishing absolute and incremental quantities is important, especially when working with derivatives (I intend to experiment with diman and Enzyme once some minor bugs are fixed to better understand the safety/usability tradeoff).
  3. Heterogeneously-dimensioned vectors (and more general linear algebra). A sufficient example is viewing tests/gas structures as vectors, supposing that only one direction is implemented (typical for equations of state for real gasses), then using Newton's method to numerically invert the transformation. Doing that naively, you have 5-vectors with heterogeneous dimension and a 5x5 matrix with matched (also heterogeneous) dimension. To use third-party linear algebra, we'd need to shed the dimensional layer, but how safe could we make it?
@Tehforsch
Copy link
Owner

  1. If I thought this through properly, in order to implement this for scalar types, one could add another entry to the dimension struct of type
struct Dimension {
    length: i32,
    mass: i32,
    ...,
    orientation: Orientation3D,
}

where Orientation3D represents a member of the group mentioned in the article and implements multiplication in the sense of that group.
Of course, this isn't the whole solution to the problem, since the mathematical functions would have to be redefined such that, for example, sin is the identity on the orientation dimension, whereas cos takes the orientation to 1_0.
In principle, I don't see anything preventing us from doing this. However, what should be guaranteed is that a user using a unit_system without any orientational analysis does not "suffer" from this additional complexity of the inbuilt sin, cos, ... functions and can just use them as before. Maybe I am not thinking it through fully, but I think it should be possible to do both, by adding additional syntax to the unit_system macro which "enables" orientational analysis. If it is enabled, the functions will be redefined to do orientational analysis.

However, in order to support non-scalar quantities, we'd first need to support heterogeneously-dimensioned vectors, as in your #3, since the components of the vectors should have different orientations.

As a side comment, there is the simpler approach of treating angles as angles, not as dimensionless quantities as defined in SI. If I understand this correctly, this kind of analysis is a subset of what can be done with orientational analysis. It comes with a lot of the benefits such as differentiating torque and energy, making conversions between Hz and rps (i.e. between omega and f) less error prone and so on.
However, it also has some similar additional complexity, where trigonometrical functions are now not of type Dimensionless -> Dimensionless anymore, but Angle -> Dimensionless instead. This wouldn't be a problem if one only ever used angles for these, but in practice, I think this is not the case, since they appear all over the place. Some constants will also appear in two forms, such as plancks constant which comes in angular and non-angular form if I remember correctly.
For some information on this see: sharkdp/numbat#91

While it is less powerful than orientational analysis, it might still be worth thinking about providing this too, because it avoids the problem of needing dimensionally-heterogeneous vectors, if I am not mistaken.

  1. This is something that I want to implement, but haven't gotten around to yet. This seems similar to differentiating covariant and contravariant vectors. I think that this should be possible in principle by adding another entry to dimension, such as
struct Dimension {
    temperature: (i32, Kind),
    ...
}

enum Kind {
    Relative,
    Absolute,
}

and then changing the definitions of some of the inbuilt functions (for example of the Add trait for quantities would have to be implemented for Relative + Relative = Relative, Relative + Absolute -> Absolute and Absolute + Relative = Absolute). I should definitely test this out in a small setup because I am never quite sure how good the compiler is at inferring that those are definitely different implementations that will never affect the same type twice.

Contravariance/Covariance and Derivatives should be representable in a similar way, if I am not mistaken.

As above, I'd like to make this an optional choice only, so that people can opt-out of the additional complexity and have a simpler unit system.

Using enzyme alongside dimensional analysis is very interesting!

  1. This is something I would like a lot. The way I currently see it, we'd implement this by disregarding the quantity type (which is homogeneous in dimension for the entire storage type) and providing a custom type that is something like (pseudo-code-ish)
struct Heterogeneous<const N: usize, const DS: [Dimension; N]>(NDarray<N>);

Of course it doesn't stop there, because we'd also need matrices (and probably even higher dimensional tensors?).

My very first experiment with this wasn't very promising:

error[E0770]: the type of const parameters must not depend on other generic parameters
  --> src/main.rs:15:59
   |
15 | struct Heterogeneous<const N: usize, const D: [Dimension; N]>([f64; N]);
   |                                                           ^ the type must not depend on the parameter `N`
   |
   = note: const parameters may not be used in the type of const parameters

So at least with the current implementation of const_adt_params, this isn't possible. Another possibility would be to "manually" unwrap the N-layer of the implementation and implementing it for a number of quantities:
Quantity1<const D: [Dimension; 1]>,Quantity2<const D: [Dimension; 2]>,Quantity3<const D: [Dimension; 3]>
etc., up to a reasonable number of dimensions. Of course this is a much uglier implementation, but with the right kind of interface around it, it could still be usable. I definitely don't expect very pretty error messages in this system. I am also assuming here that very high N don't appear in these kinds of systems, but I am really only familiar with the typical state vectors of (density, velocity, energy) or something alike, so perhaps there are some other disciplines where large numbers of N are required.

This could be possible, but would require quite a bit of macro code. It's something that could be tested quite quickly.

Interfacing with the linear algebra library is the next problem of course. I'd probably start by picking a single library and then trying to implement some of the basic methods for all of the QuantityX types above. I don't see anything that is stopping this right now, but it would have to come down to a test.

As a more general comment that seems like it might be relevant here: One thing that bothers me is that everytime more information is added to the Dimension struct in order to perform stricter analysis, the amount of information included in the compiler error messages increases, making it harder to see the dimension mismatch on a first glance. One thing that would be super helpful here is if I could somehow inject custom formatting code for my const generic structs that tells the compiler how to display them in error messages. Unfortunately I am not aware of this being an option. I tried looking into how/where the compiler even displays these structs (because it does this for structs that don't have a Debug implementation), but wasnt even remotely familiar enough with the rustc code to understand this.

@jedbrown
Copy link
Contributor Author

jedbrown commented Dec 7, 2023

Thanks for writing this out.

  1. Having not used an orientational analysis or dimensional-angles system, my impulse is that dimensional angles would be easier to use while still catching many of the mistakes that the current system allows. And it should be easier to implement.
  2. Distinguishing covariance and contravariance would be amazing when working in non-orthogonal coordinate systems. I don't know how what of that belongs at this level versus tensor algebra library based on diman.
  3. I'm fearful of the explosion this would cause, and wonder if we could do something based on structs. Like if tests/gas made Primitive and Conservative indexable, then we could have conversion routines to Vector<const Struct: AsVector> with impl AsVector for Primitive defining the length and conversion between the struct and array representations. The intent here is that adding a Vector<Primitive> to a Vector<Conservative> would name those types in its error message, rather than extreme verbosity of each entry. This is a rough sketch and may have serious issues -- it feels like a messy problem.

In my applications, the small matrices/vectors are assembled into large ones (dimension in the millions), with some rows/columns eliminated for boundary conditions so there isn't a trivial pattern identifying dimensions for any given entry (and in any case, the size is also not known at compile time). As for libraries suitable for the large sizes, faer is by far the most interesting. I think it's not super relevant here because typing likely only applies to statically-dimensioned matrices. nalgebra is probably the most popular for statically sized matrices.

@Tehforsch
Copy link
Owner

I created three new issues for each of the separate topics and will close this instead.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants