Welcome to the Onshape forum! Ask questions and join in the discussions about everything Onshape.

First time visiting? Here are some places to start:
  1. Looking for a certain topic? Check out the categories filter or use Search (upper right).
  2. Need support? Ask a question to our Community Support category.
  3. Please submit support tickets for bugs but you can request improvements in the Product Feedback category.
  4. Be respectful, on topic and if you see a problem, Flag it.

If you would like to contact our Community Manager personally, feel free to send a private message or an email.

A number of issues with equation support

neobobkrauseneobobkrause Member Posts: 105 EDU
edited January 2016 in Using Onshape
The OnShape equation solver seems unnecessarily restrictive and fragile. I'm finding myself spending an inordinate amount of my design time trying to understand why an expression is being flagged as invalid and trying through trial and error to find workarounds.

Here are some examples. I'll have to show errors as struck-out text as the html editor doesn't allow me to color text.


Variable #a = 2 mm
Variable #b = #a*3

I know that the documentation gives some explanation for why this is an error, but it shouldn't be. It's unambiguous. If the equation grammer does in fact render this ambiguous, then I suggest that perhaps the grammer definition should be re-examined.


Variable #a = 2 mm
Variable #b = 3 mm
Variable #c = #a/#b

I understand the rational given for why this is flagged as invalid. Yet a designer might have a legitimate need for something like this when, for example, calculating a diagonal. I would even suggest that the solver accept a units divisor when the units are different than the numerator.


A related suggestion: The equation parser should minimally include a value() function that strips away the units of the given expression.


I think the size of the textedit control that an expression is entered in when setting dimensions or other contraints needs to be appropriately large given the length of the expression. I sometimes end up needing to paste an expression into the windows of some other application so that I can see the full expression as it's being constructed and edited.

I also think an expression editor control that includes syntax coloring and substring completion would add immensely to the UX.

I can appreciate OnShape's variable support is a fairly new feature. As such, I'm encountering a fair number of idiosyncratic quirks that remain that I end up investing a non-trivial amount of time on trying to determine whether the error is in my expression or an OnShape quirk. User productivity will improve as the grammer matures and these quirks are identified and resolved.

- Bob

Comments

  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 1,216
    Both of the examples you gave work correctly for me: https://cad.onshape.com/documents/a504c217a43a41ffb3d39e96/w/56865c1f5ff2488bb14f48b6/e/95ba7f21d6144b45bc53491b
    What am I missing?  Except that in the second example, you want #a/#b instead of a/b.
    Ilya Baran \ VP, Architecture and FeatureScript \ Onshape Inc
  • neobobkrauseneobobkrause Member Posts: 105 EDU
    Yes, I mis-entered the expression for #c. I've edited the post. 

    As I say, it's sometimes difficult to determine why an expression throws an error. I have a drawing that includes two variables, let's call them #BaseHeight and and #RimHeight, that I want to define like this...

    Variable #BaseHeight = .2 mm
    Variable #RimHeight = ceil(#BaseHeight*3)

    As noted from my poor-man's styling, the second definition throws an error for some reason I've been unable to determine.

    Any suggestions are appreciated.

    - Bob
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 1,216
    Agreed that we have a lot of work to do on error reporting.  We are working on it.  Autocomplete is also on our todo list.

    The expression above won't work because the ceil function won't take a length.  The reason it won't is that depending on the units that the length is expressed in, the result will be different: what you'd expect from ceil(2.54 cm) will be different from ceil(1 in) even though the two lengths are the same.  And it's not clear at all what to do with ceil(1 in + 3 m^2 / (2 cm)).  For the same reason, the value function you propose would not work.  I would argue that a system where if I enter the same length in different units and the behavior downstream changes has a serious problem.

    The way to do what I think you want to do here is to explicitly specify which units you want to use ceil in as follows:
    Variable #RimHeight = ceil(#BaseHeight*3 / mm) mm
    Ilya Baran \ VP, Architecture and FeatureScript \ Onshape Inc
  • neobobkrauseneobobkrause Member Posts: 105 EDU
    Thanks for the workaround. Your's a lot more helpful than what I'd temporarily done, which is simply hard code the value (.2*3).

    Solutions aren't always easy or obvious. And I'm probably not the best person to suggest a solution that works syntacticly, semantically and UX-ally. But let me ask some questions if you'll allow me.


    You pointed to the confusion that would result from the expression: ceil(1 in + 3 m^2 / (2 cm)).  

    Let's break it down. What's the resulting value of (1 in + 3 m^2 / (2 cm))? The solver can evaluate this expression deterministically, right? (If it can't, then I'd wonder why not and ask whether it should.) Whatever that value is, can ceil() handle it? If not, is the reason because ceil() doesn't take lengths? Again, why not? If both the value and units of the expression passed to ceil() were in fact deterministic, then there would be no ambiguity about how to perform a ceiling operator on an amount expressed in those units. The precedence rules for expressions shown in the OnShape documentation seem to properly include units. 

    I have another question related to how units are expressed in an equation. Sorry for not being able to figure this out on my own. What does (#BaseHeight*3/mm) mean? Why the slash between the constant and the unit designator, and how does that differ from (#BaseHeight*3*mm)? I ask because I see that when I enter a reference to a non-units variable in a dimension constraint it gets converted to something like (#a)*millimeters. What up?

    Thanks for your help.

    - Bob
  • thomas_kozakthomas_kozak Member Posts: 38 ✭✭
    edited January 2016
    Ilya,  the issue is with defined units.  If you enter "0.2" for one of Bob's examples, it works, but if you enter "0.2 mm" it will fail.
  • neobobkrauseneobobkrause Member Posts: 105 EDU
    I have another question potentially related to equations.

    Suppose I define a variable #a. Further suppose that I reference this variable in an equation elsewhere in my document. If I later change the name of #a to #b, it appears that equations containing the variable reference doesn't get automatically updated to reflect the new name. Rather, the designer must manually change the text of every equation with references.

    It's been my experience that it's important that development environments support refactoring scenarios like this.

    - Bob
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 1,216
    edited January 2016
    It does take a while to wrap your head around the units issues -- I should probably do an under-the-hood type blog post about this.

    Here are loosely organized thoughts in the meantime.  The way to think about units in Onshape is that when you have an expression like "2.5 cm", you have an actual length -- the length itself doesn't know what units it's expressed in -- "2.5 cm" can be interchanged with "25 mm" in all situations and will work absolutely identically.  I believe that this is a critical property of a well-functioning system.

    Let's break down the expression I wrote, "1 in + 3 m^2 / (2 cm)".  "1 in" is a length.  "3 m^2" is an area.  "2 cm" is a length.  "3 m^2 / (2 cm)" is an area divided by a length, hence a length.  The whole expression is therefore two lengths added together and therefore evaluates to a length.

    Determinism is not an issue here -- when you enter a length, we deliberately immediately forget the units in which the length was entered.  To put it another way, w.r.t. "ceil", what is your height rounded up?  That question doesn't make sense without also saying something like "to the next centimeter".

    To explain my workaround, "#BaseHeight*3/mm" is a length times a unitless quantity (3) divided by a length (mm), hence it's a unitless quantity that ceil can process.   "#BaseHeight*3*mm" is two lengths multiplied together, hence an area.  We can't round up areas.

    Does this help?

    Regarding your question on renaming, yes, we have another TODO to support this -- for now that is unfortunately a pain.
    Ilya Baran \ VP, Architecture and FeatureScript \ Onshape Inc
  • neobobkrauseneobobkrause Member Posts: 105 EDU
    Thanks Ilya for your detailed response. Let me see if I understand...

    You're saying that it's important to appreciate that an equation may contain subexpressions with units from different domains -- length and area in your example. Though a single expression can refer to quantities from different domains, there are inherent mappings across domains that are intrinsic to the grammer. Other programming grammers may rely on the use of builtin functions to cross domains, like "ptIn3D = new Point(2,3,4)". However OnShape's grammer includes intrinsic operators that both create a measure in a domain, like "2mm", and cross domains, like "3 m^2 / (2 cm)". As you say, the grammer is sufficiently deterministic to allow measures to be expressed, to convert between measures within a domain, and to invoke intrinsic mappings across domains. 

    As such, each node in a tree that results either from the parsing of an expression or some other internal tree generator is a (value, domain) tuple. The problem with this representation is that it requires that all values be represented as a value normalized to the intrinsic unit of measure for that domain. For example, distance may well be represented internally as a floating point number of millimeters -- even if the distance were originally expressed in yards.

    Yet you say OnShape can correctly interpret the expressions "ceil(3mm)" and "ceil(3ft)". How? Why? Is it that the single leaf-node trees that result from the parsing of "3mm" and "3ft" are actually (value, domain, unit) tuples that preserve values in the unit of measure they are expressed in? If so, then why can't all nodes of the tree be 3-tuples like that? This would allow significant syntactical simplification and and a more productive UX, because this set of intuitively unambiguous expressions could be accepted...

    Variable #a = 2mm
    Variable #b = ceil(#a*3)

    - Bob
  • ilya_baranilya_baran Onshape Employees, Developers, HDM Posts: 1,216
    First, a correction: I say that Onshape can *not* (and should not) evaluate the expressions like "ceil(3 mm)".

    Now let's talk internal representations :smile: 

    You are right -- when we evaluate the expression "3 ft + 2 in", we internally store the value in meters and that it is a length -- (0.9652, meter) in your terms.

    The problem with using something like the triplets (0.9652, meter, originallyExpressedInFeet) is that now 3 feet is not the same thing as 36 inches -- because they are stored as different triplets.  Worse, if we do something like go with the unit that comes in some way first in the expression, we may get that "3 ft + 2 in" is different than "2 in + 3 ft" -- that's really unintuitive!

    Here's another way to think about the problem with the triplets scheme: yes, when designing, you may want to write an expression like 
    ceil(#a*3) where you know #a is a length expressed in millimeters.  But the next person who looks at your model and needs to understand what's going on will have to think about not just the value of #a (the length it actually represents) but will have to track down the units it was originally expressed in.  The way I think about expressions like "ceil(#a * 3 / mm) * mm" is that it's actually documenting what's going on: I'm taking the length represented by #a, counting how many millimeters it is, rounding that number up, and taking that many millimeters.
    Ilya Baran \ VP, Architecture and FeatureScript \ Onshape Inc
  • neobobkrauseneobobkrause Member Posts: 105 EDU
    As stated in my original posting, as a fairly new OnShape user, I experience the equation solver to be fragile and quirky. These idiosincraticies require significant time investments to resolve, workaround or just plain punt on. Ilya, I appreciate the deeper understanding of the basis of this quirkiness that I'm getting from this discussion. I also wonder whether opportunities exist to improve users' equation-related experiences. Let me explore that question by diving further into your last response...

    The problem with using something like the triplets (0.9652, meter, originallyExpressedInFeet) is that now 3 feet is not the same thing as 36 inches -- because they are stored as different triplets.  Worse, if we do something like go with the unit that comes in some way first in the expression, we may get that "3 ft + 2 in" is different than "2 in + 3 ft" -- that's really unintuitive!

    I don't know how unintuitive the user experience would actually be. Addition is a transitive operation. If the equation grammer is extended to include precedence rules related to units of measure, the distance value that results from both operations, "3 ft + 2 in" and "2 in + 3 ft", would be the same. What I'm suggesting would affect the third element of the 3-tuple -- the original unit of measure.

    Let's get concrete. Suppose the OnShape equation grammer is updated with the addition of precedence rules for units of measure. Not to prejudice what those rules should be, but suppose a rule is added that says the unit of measure of the first term of an addition defines the result's unit of measure. With that rule in place, implemented using a 3-tuple like the one I've described, the separate results of these two equations would be...

    "3 ft + 2 in" = (0.6604, distance, feet)
    "2 in + 3 ft" = (0.6604, distance, inches)

    Notice that the distance value is the same in both, because all distances are stored as quantities of meters. The only difference is the element that references the scale the value was originally expressed in by the first term. A grammer that includes a complete set of unit of measure rules would preserve this scale deterministically. So functions like ceil() would be able to properly handle all intuitively obvious expressions, like...

    ceil(#a)
    ceil(#a*3)
    ceil(3*#a)

    It may be worth explicitly saying that addition's transitive nature is unaffected by the grammer rules extensions. The extentions would also be fully backward compatible. 

    Here's another way to think about the problem with the triplets scheme: yes, when designing, you may want to write an expression like ceil(#a*3) where you know #a is a length expressed in millimeters.  But the next person who looks at your model and needs to understand what's going on will have to think about not just the value of #a (the length it actually represents) but will have to track down the units it was originally expressed in.  The way I think about expressions like "ceil(#a * 3 / mm) * mm" is that it's actually documenting what's going on: I'm taking the length represented by #a, counting how many millimeters it is, rounding that number up, and taking that many millimeters.

    The rule extensions I'm suggesting would continue to allow explicit references to units of measure, while also allowing users the flexibility to capture the unit of measure in a single place (the variable definition) rather than repeating it in every variable reference. This would have potential benefits in some refactoring use cases.

    - Bob
  • neobobkrauseneobobkrause Member Posts: 105 EDU
    edited January 2016
    if the grammar were extended to include deterministic support for units of measure, a units() built in function would allow values and variables to be created and saved (in variables) with a common unit of measure.

    Consider the following possible syntax to see what I mean...

    Variable a = 3 mm
    Variable b = value(#a) units(#a)
    - Bob
Sign In or Register to comment.