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

polymorphic_unit??? #483

Open
mpusz opened this issue Aug 27, 2023 · 14 comments
Open

polymorphic_unit??? #483

mpusz opened this issue Aug 27, 2023 · 14 comments
Labels
design Design-related discussion iso The ISO C++ Committee related work question Further information is requested

Comments

@mpusz
Copy link
Owner

mpusz commented Aug 27, 2023

Sometimes, people want to deal with quantities of a specific type but with a unit unknown at compile-time. For example, a specific unit might be obtained from some configuration file or from the web by some means. Right now we are forced to use std::variant but it is far from being easy and user-friendly to use.

Maybe something like a polymorphic_unit should be considered?

@mpusz mpusz added question Further information is requested design Design-related discussion labels Aug 27, 2023
@JohelEGP
Copy link
Collaborator

Sometimes, people want to deal with quantities of a specific type but with a unit unknown at compile-time.

I know of at least two use cases that warrant different solutions.

  • A format specifier for runtime scaled output.
    E.g., a value of type quantity<si::metre, double>,
    that formats to "1 km" when it equals 1000 * m, and
    formats to "1 mm" when it equals 0.0001 * m.
  • An actual quantity type whose reference is runtime scaled.
    So the value of the reference is stored at runtime rather than compile-time.

For the former,
I think std::format actually needs a value like scaled(x, si::ratios),
so the algorithm internal to the formatter know which scales to use.

For the latter,
we can support it by making reference a non-static data member.
But the current implementation of reference uses magnitude,
which isn't runtime friendly.
So much more massaging is necessary for this approach to work.

@mpusz
Copy link
Owner Author

mpusz commented Aug 27, 2023

I would say that the former is out of scope for this issue. It can be addressed by providing a specific token in a grammar, and the implementation would be similar to the capacitor_time_curve example.

@chiphogg
Copy link
Collaborator

chiphogg commented Sep 4, 2023

I wanted to note my comments from our last meeting here, for posterity.

  1. This is probably a good idea. I believe there are use cases for this. One example is speed limits, which are usually integers, but which can be represented in MPH or KPH in different contexts. Users can get everything they need from variants, but using variants directly can be cumbersome, so there's value in providing a more usable interface.

  2. We probably shouldn't do it. This is purely for reasons of opportunity cost. We already have more work than we can do in the time we have. We're better off waiting for an actual, concrete use case to arise --- probably via an issue filed by a user with a specific problem to solve. Note too that the nature of this feature is that it's very easy to add on after the fact. We won't regret standardizing the rest of the library without this feature, but we easily could regret standardizing a suboptimal implementation of this feature.

Therefore, I suggest we close this issue and reduce our list of open design issues.

@mpusz
Copy link
Owner Author

mpusz commented Sep 4, 2023

We probably shouldn't do it.

Of course, I do not claim we should implement it now, but at least consider if we find it useful and discuss the implementation cost.

Note too that the nature of this feature is that it's very easy to add on after the fact.

I am not so sure it is that easy to add later. As a concrete unit will be only known at runtime, we will need at least a few important changes:

  • polymorphic_unit_of<QuantitySpec> that type-erases the actual unit and enforces it at compile-time to only be of a compatible quantity type.
  • quantity and reference (and maybe more types) will need to store the polymorphic_unit as a member to preserve the information at runtime. This requires either partial specializations for the current case and the polymorphic one or using something like EBO or [[no_unique_address]] in the same class template definition.

Does anyone see any other important points that require change with this feature?

@JohelEGP
Copy link
Collaborator

JohelEGP commented Sep 4, 2023

Does anyone see any other important points that require change with this feature?

We don't have types to represent non-integral amounts of an unit.

@chiphogg
Copy link
Collaborator

chiphogg commented Sep 4, 2023

I assumed we would build the feature non-intrusively, on top of quantity. But if we did that, I guess it would be more like "polymorphic quantity" than "polymorphic unit".

It's true that a kind of "variant of units" might require a more invasive approach. But I also don't see the value that would add over and above the "variant of quantities" approach.

@mpusz
Copy link
Owner Author

mpusz commented Sep 4, 2023

We don't have types to represent non-integral amounts of an unit.

Why would we need it? My understanding is that we will be working with proper units but only known at runtime, so the conversion will happen at runtime in sudo_cast implementation based on the amount/factor/ratio described by a specific unit pointed by the type-erased one. It is somehow similar to the runtime currency conversion factor that I implemented some time ago but as we decided to not follow this path then, the branch was removed.

@JohelEGP
Copy link
Collaborator

JohelEGP commented Sep 4, 2023

I suppose it's doable if the amount of unit is a double that's eagerly evaluated.
Unlike mag, which keeps the roots around.

@Attempt3035
Copy link

I wanted to note my comments from our last meeting here, for posterity.

  1. This is probably a good idea. I believe there are use cases for this. One example is speed limits, which are usually integers, but which can be represented in MPH or KPH in different contexts. Users can get everything they need from variants, but using variants directly can be cumbersome, so there's value in providing a more usable interface.
  2. We probably shouldn't do it. This is purely for reasons of opportunity cost. We already have more work than we can do in the time we have. We're better off waiting for an actual, concrete use case to arise --- probably via an issue filed by a user with a specific problem to solve. Note too that the nature of this feature is that it's very easy to add on after the fact. We won't regret standardizing the rest of the library without this feature, but we easily could regret standardizing a suboptimal implementation of this feature.

Therefore, I suggest we close this issue and reduce our list of open design issues.

Hey all! I've got a use case that might be relevant here, and while I'm only just getting started with this library (and may have missed an obvious way to do this), I think it's probably a likely scenario in other use cases too!

I have a scenario where I'm sending data between a simulation (one of a few variants) and an endpoint. The data has a key which refers to what it's for (ie, a car's tachometer) and it's corresponding value. I'm looking to also send the data with a unit, such as revs/m, but knowing that the unit might be different from different simulations, for the same data. I'd then need to do a runtime conversion to the unit appropriate for the endpoint.

It would be suuuuper easy to write the implementation if I can convert between two unknown types like this at runtime safely.

Secondly to all this, is there any possible means of having an enumeration or similar unique identifier for each unit type? As in, I'm not sure of a way the data type can be represented / serialised and cast back to the safe, correct type at the other end. My thoughts at the moment is that it would be great if there was a reliable mapping (maybe that is also versioned and values aren't reused so old and new versions can still interface nicely) of types to enumerations (which a getter function with a polymorphic return would work well with) so that data can be encoded, sent, decoded and operated on while keeping all the great aspects of using this library's classes.

Thoughts?

@chiphogg
Copy link
Collaborator

chiphogg commented Feb 4, 2024

Hi @Attempt3035! Here are a few thoughts.

First: if it's natural for your simulation variants to know the unit that the endpoint requires, the easiest solution would be to convert the values into that unit before serializing them. You could indicate that unit via a suffix on the name of the serialized variable: for example, if the endpoint wants a speed in m/s, you could name the serialized field speed_mps.

As for a serializable enum: I think that's going to depend on the set of units that are relevant for an individual use case, which is not something we could know in a general way. For example, you could create a serializable enum with values for m/s, MPH, KPH, and whatever other speed units you need. And you could serialize a quantity of any of these units by serializing its value in that unit, along with the corresponding enum value.

I don't anticipate being able to support an unbounded set of units in this way, though. It would raise too many questions in my mind; I worry about excessive complexity. What information about the unit would we encode? Its name only, or also its symbol, dimension, magnitude, quantity kind, ...? What if two different programs define two different units with the same name? And on the decoding end: if we encounter a quantity that was serialized with a unit we don't have access to, what should we do?

For data on the far side of the "serialization boundary", experience has taught me to prefer representations that are both simple and unambiguous. Examples include a single fixed unit indicated by a suffix on the field name, or a value-plus-enum where the latter can indicate one of a predetermined set of unit options.

@Attempt3035
Copy link

Hey @chiphogg! Those are some really good points you make!

For my system architecture, we try to keep the simulations decoupled and considering the simulation plugins can be in different languages, we don't want to rewrite conversion code for each to cater for every case the endpoint could ask for. In this way, we have the c++ engine in the middle that bridges everything and performs the safe conversions, hence the need to serialize and send the unit types.

Yes, defining a project specific enum is probably appropriate. You raise great points about what it would or wouldn't include, there's likely too many possible combinations for a universal solution to make sense. I guess my initial thought was that it would act sort of like a capnproto message that would be backwards compatible across versions, but that sort of functionality is definitely beyond the scope of the project. I guess it sort of makes it more like a platform independent data type binding system (although that would be handy😝)

Yes, I guess I'll keep it implementation specific and define an enum with just the units that get used and define the conversion function to deserialise the data into the appropriate unit type. It will be easy to do unit conversions from there on out anyway! Hmm, haven't looked into it much yet but it might be quite annoying to define the return type for that function as the list grows, I think I could use something like boost.any? But I do wonder how a polymorphic return type would work in comparison as far as improving compile time safety and minimising lengthy variants... Once I dive further into the code I'll see how it goes😝

@kwikius
Copy link
Contributor

kwikius commented Feb 19, 2024

FWIW I found that the cases for a polymorphic unit are often limited to a few units {mph , kph } { in, cm, m } in a user interface. This can be dealt with by a simple parser and a map e.g https://github.com/kwikius/quan-trunk/blob/master/quan_matters/examples/quantity_map.cpp#L86 . Might be an interesting example to add to mp-units

@Attempt3035
Copy link

That's an awesome help, thank you!

@mpusz mpusz added the high priority EXTRA URGENT label Feb 28, 2024
@mpusz mpusz added iso The ISO C++ Committee related work and removed high priority EXTRA URGENT labels Jun 22, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
design Design-related discussion iso The ISO C++ Committee related work question Further information is requested
Projects
None yet
Development

No branches or pull requests

5 participants