Skip to content
This repository was archived by the owner on Feb 3, 2023. It is now read-only.

Issue #302: Add common math functions to RQuantity#445

Merged
Octogonapus merged 12 commits intoOkapiLib:developfrom
breqdev:feature/WJC/#302_add_math_functions_rquantity
Jul 10, 2020
Merged

Issue #302: Add common math functions to RQuantity#445
Octogonapus merged 12 commits intoOkapiLib:developfrom
breqdev:feature/WJC/#302_add_math_functions_rquantity

Conversation

@breqdev
Copy link
Contributor

@breqdev breqdev commented Jul 6, 2020

Description of the Change

Add common math and trig functions (sin(), cos(), sqrt(), etc.) to RQuantity (units API). This PR includes many of the functions present in the header (trigonometric and hyperbolic functions, a few basic root functions, modulus, etc). It does not include rounding functions or log/exponent functions.

Motivation

Previously, using common math functions with the units API required converting a number from an RQuantity to a double, using the function, and then converting the number back to an RQuantity. This PR adds functions like sin() that take RQuantity objects as arguments so users do not need to do this conversion.

Possible Drawbacks

Because these functions are implemented as free functions as per this comment, they might clutter up the namespace a bit.

Verification Process

I tested each function I implemented on my desktop to ensure it gave valid results. (I haven't gotten the chance to test on a V5 brain, but compilation succeeded when I copied these changes into a new PROS project. Since most of the work done by the units API is at compile time, this seemed like a good sign.)

Applicable Issues

Closes #302.

@breqdev
Copy link
Contributor Author

breqdev commented Jul 6, 2020

It looks like the build failed. (I didn't think to run a make all before committing, because it didn't seem like these changes would have any effect on other parts of the code.)

I think this might be because the C sin(), cos(), etc. defined in math.h can't be overloaded? Several places in the OkapiLib code use these functions instead of std::sin() etc. defined in <cmath>.

I'm honestly not sure how best to proceed from here. I could put the functions into their own namespace, or make them member functions of RQuantity, but that doesn't seem ideal.

Copy link
Member

@Octogonapus Octogonapus left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for taking the time to do this PR. I just had some small comments.

@theol0403 Can you comment on what may be missing from this PR?

@breqdev
Copy link
Contributor Author

breqdev commented Jul 6, 2020

Thanks for the feedback. To clarify, if (for instance) sin() returns only a unitless number, should it accept only an angle? Also, if the trig functions only work with angles and numbers, would it make more sense to put them in QAngle.hpp, or to have all the functions together in RQuantity.hpp?

It looks like there are more files that use (e.g.) sin() instead of std::sin() that are preventing the build from passing. I'm happy to go through and fix these if you want, but I don't really have a way to test the result.

@Octogonapus
Copy link
Member

To clarify, if (for instance) sin() returns only a unitless number, should it accept only an angle?

Yes.

Also, if the trig functions only work with angles and numbers, would it make more sense to put them in QAngle.hpp, or to have all the functions together in RQuantity.hpp?

Leave them in RQuantity.hpp.

I'm happy to go through and fix these if you want, but I don't really have a way to test the result.

Go ahead and change them.

@breqdev breqdev requested a review from Octogonapus July 6, 2020 18:40
Copy link
Member

@theol0403 theol0403 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good to me, you've included all the useful functions.

I know you said it does not include rounding functions or log/exponent functions, but if possible those would be useful (especially pow). Pow would probably look different than the other methods, as the exponent would probably have to be given as a template parameter for it to know the return type. A good compromise would be a square and cube method.

Rounding methods like floor and ceil are also useful, but I can't think of a good way to express them in terms of units. An API such as floor<degree>(0.6_deg) might be possible, but that might belong to a separate PR.

@Octogonapus
Copy link
Member

Octogonapus commented Jul 6, 2020

Pow would probably look different than the other methods, as the exponent would probably have to be given as a template parameter for it to know the return type. A good compromise would be a square and cube method.

I agree. @Breq16 can you try to implement pow this way?

Rounding methods like floor and ceil are also useful, but I can't think of a good way to express them in terms of units

I agree they are useful and I don't think it would be difficult to implement. Wouldn't round, floor, and ceil simply return the same dimensions as they accepted?

@theol0403
Copy link
Member

theol0403 commented Jul 6, 2020

I agree they are useful and I don't think it would be difficult to implement. Wouldn't round, floor, and ceil simply return the same dimensions as they accepted?

The problem is that the rounding depends on the internal unit - round would work differently with degrees than radians for example, so we would need to specify what unit we want to round to.

@Octogonapus
Copy link
Member

The problem is that the rounding depends on the internal unit - round would work differently with degrees than radians for example, so we would need to specify what unit we want to round to.

Oh, right. I forgot about that. I think it makes sense that we'd require the user to supply a specific unit to round in. For example, floor<inch>(1.2_in) == 1_in, round<meter>(47_in) == 1_m.

@theol0403
Copy link
Member

Finally, I think it would be a good idea to write some simple tests for the functionality of these new methods. Even if the highest chance of something breaking is within compile-time, there is always a possibility of having issues with certain types, or silly things like swapped parameters. Testing is definitely necessary for the rounding methods if you choose to implement them.

Writing tests are fairly straightforward with googletest, but if you aren't comfortable with the testing system I'm happy to write them.

@breqdev
Copy link
Contributor Author

breqdev commented Jul 7, 2020

I had a working implementation of pow() that used a std::ratio as a template parameter (so cubing a number would be e.g. pow<std::ratio<3>>(3_in)). The syntax isn't ideal (which is why I didn't include it in the initial PR), but I can't think of a better way to implement it. I'd be happy to add this as well as some more user-friendly square() and cube() functions.

Correct me if I'm wrong here, but it seems like the syntax for the rounding would be floor(1.2_in, inch), not floor<inch>(1.2_in), because inch is an instance of QLength, not a type? If so, implementing the rounding functions this way shouldn't be a problem.

It seems like the other log and exponent functions wouldn't really work because of the way units are handled (for instance, log(1_m) and log(1_ft) can't both return zero).

I don't really have any experience writing tests, but I could give it a try. (It seems like the correct place to add them is in test/unitTests.cpp?) I'll read the googletest documentation and try to write some.

@theol0403
Copy link
Member

For pow, you should be able to get away with accepting an int as the template parameter (it's one of the few things that templates allow), and doing pow<3>(3_in).

Humm, you are correct. I'm not actually sure how you would go about this then - the unit parameter would be an RQuantity of the same dimension as the value parameter, but then the only thing that you can use to tell the units apart is the ratio to their underlying unit (inch.getValue() == 0.0254). Maybe it would work if you divided the value by the unit ratio, used the rounding function, and then multiplied the value by the ratio.

Yep, that's where you should put the unit tests. You just make a test case, and the testing framework will automagically add it to the tests which the CI will run. You can also run the tests locally by running the cmake commands outlined by the azure build file (in the project root).

@breqdev
Copy link
Contributor Author

breqdev commented Jul 7, 2020

Cool, I'll get to work on the unit tests.

With regards to pow, it seems like it'd be best to allow users to raise an RQuantity to a fractional exponent as well (such as pow<std::ratio<1, 4>> for a fourth root). It seems like it's possible to have both this version and the nicer int version. Is it worth keeping both, or just the int version?

@theol0403
Copy link
Member

theol0403 commented Jul 7, 2020

Yeah, I'd keep them both as overloads. It might also be nice to have a root<int> method to compliment the pow<int> method, and then the pow<R> is to do both.

@Octogonapus Octogonapus merged commit 4f278f0 into OkapiLib:develop Jul 10, 2020
@breqdev breqdev deleted the feature/WJC/#302_add_math_functions_rquantity branch July 10, 2020 18:58
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

Comments