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

dimensional order of operations / moderately complex formulas #64

Open
td002 opened this issue Sep 19, 2023 · 2 comments
Open

dimensional order of operations / moderately complex formulas #64

td002 opened this issue Sep 19, 2023 · 2 comments

Comments

@td002
Copy link

td002 commented Sep 19, 2023

I suspect this may be a difficult fix for the fixed-dimension-type design model, but I like the intent of this module, and ran into the problem pretty quickly, so I'll let you know.

I could also be wrong --- and it's an easy fix --- or wrong in that I'm using the module incorrectly.

The problem seems to be related to order of operations and comparison to the pre-defined types. It seems like if any part of an operation doesn't match one of the predefined types --- even if the dimensions will later cancel to match --- the units get dropped and calculation proceeds without units.

Impractical illustrative simple test case

my $a = 1kg;
my $b = 1kg;
my $c = ♎️<1 m/s^2>;
this works fine:
my $d = $a * $c;
say $d.in('N');
output: 1N

something like this should also work, but it doesn't. Seems to choke on the
idea of a square kilogram before the dimension-canceling takes place.
my $e = $a * $b * $c / $b ;
say $e.in('N');
output: No such symbol in method multiply at (Physics::Measure) line 254
say $e;
output: 1

Practical test case

Calculation of hydraulic power

my Flow $Q = Flow.new( value => 1, units => 'm^3/s' );
my Length $h = Length.new( value => 10, units => 'm' );
my Density $d = Density.new( value => 1000, units => 'kg/m^3' );
my Acceleration $g = Acceleration.new( value => 9.81, units => 'm/s^2' );

my $P = $Q × $h × $d × $g;

say $P;
output: 98100
say $P.in( 'kW' );
output: No such method 'in' for invocant of type 'Rat'.`

Other program examples

gnu units, udunits2, orpie, hp calculators can usually do this type of thing, as long as the end result is dimensionally consistent with whatever unit you're trying to express/convert after the operation is complete.

gnu units

units -t -v "(1 m^3/s)*(10 m)*(1000 kg/m^3)*(9.81 m/s^2)" kW
output: 9.81 kW

mixed-unit systems also work fine, as long as result is consistent
units -t -v "(100 gallons/min)*(10 ft)*(9.81 m/s^2)*(62.4 lb/ft^3)" horsepower
output: 0.25286413 horsepower

udunits2

udunits2 -H "(1 m^3/s)*(10 m)*(1000 kg/m^3)*(9.81 m/s^2)" -W kW
output: 9.81 kW

@librasteve
Copy link
Owner

@td002 - many thanks for trying this module and for the feedback. I did indeed have an early version of the code that synthesized "artificial" units for intermediate results where there is no predefined unit to be found, this did not make it to the current release

I agree that this is a worthwhile new feature and therefore will leave this issue open and aim to implement in the next major release

I am struggling a bit with tuits on this right now, so please feel welcome to make the change yourself and submit a PR

Since you have knowledge of the wider range of unit libraries, perhaps I should consider refactoring this module around a more widely used lib - which one would you say is the best target (mainly for linux type systems for now)?

@td002
Copy link
Author

td002 commented Sep 20, 2023

I'm learning Raku (from Perl) --- I'll see if I can follow the logic to see if there's a quick fix I have the skill to implement.

wrt unit libraries or libraries that could be used for refactoring, here's what I know that might be useful to you.

UDUNITS-2

In terms of a well-tested pure unit library that has an API that could be accessed through NativeCall, AFAIK there's only UDUNITS-2. It provides basic math operators (+,-,*,/,**,root,log) for right-side-only calculations.

There's a perl module (Physics::Udunits2) that uses XS to access the API that would probably give you a head start if you wanted to use this.

GNU Units

GNU Units is also very good, but no official API.

Giac

I'm a practicing civil engineer and haven't the skill to make even a partial NativeCall port of this system --- but I do often use the Xcas/Giac computer algebra system for these sorts of calculations. Giac is the CAS engine, and has a C++ API. It also supports a wide array of units that you can tag to values. Giac is used as the CAS in HP Prime and Numworks calculators.

In a lot of ways unit management is a problem for a CAS to solve. So with Xcas/Giac you can supply an equation, and then substitute values with whatever units you have without the need for conversion, then solve for the missing variable on either side of the equation, and get the answer in whatever (dimensionally consistent) unit you need. e.g.

hyd_power := P = Q*h*d*g
comment("note solve returns a list, have to use the [0] subscript")
convert( solve( subst( hyd_power, [ Q = 100_(galUS/mn), h = 10_ft, g = 9.81_(m/s^2), d = 62.4_(lb/ft^3) ] ), P )[0], _W )
output: 188.560747713_W
comment( "now solve for head in feet")
convert( solve( subst( hyd_power, [ Q = 100_(galUS/mn), P = 188.5607477_W, g = 9.81_(m/s^2), d = 62.4_(lb/ft^3) ] ), h )[0], _ft )
output: 9.99999999933_ft

It is difficult to overstate how much mental overhead it saves an engineer to be able to do something like this. And yet, most other engineers I know are still using fixed-unit formulas with spreadsheets or calculators to do these sorts of quick calculations. They just rearrange the equation by hand to get the function they need. Most of the time it's fine because they're working with the same units all of the time and the physical constants and unit conversions get compressed into a constant that saves some time --- at the cost of flexibility.

There is a python interface for Giac. If I had the time to try to understand how they make the bridge from python variables to Giac variables, and I also somehow suddenly acquired the skill to do that in Raku, I'd give it a go with NativeCall.

But alas, I don't have enough jar for all of my rocks, nevermind the sand.

There's a simple perl module (Math::Giac) that basically just gives you a way to assign values to variables with a hashref and then builds a script that gets passed to the Giac executable --- it doesn't use the API. It works for what it does, but it looks like something I would write.

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