Operator_multiply(float,float) on instance null

  • Platform information:
    • Hardware: 4 Intel CPU_CPUs, 8GB RAM
    • OS: Debian 10
    • Java Runtime Environment: OpenJDK 11
    • openHAB version: 3.2.0

Problem description:
I want to multiply float values in a rule but get the following error message:

Script execution of rule with UID 'KG_Heizraum_Heizung-2' failed: An error occurred during the script execution: Could not invoke method: org.eclipse.xtext.xbase.lib.FloatExtensions.operator_multiply(float,float) on instance: null in KG_Heizraum_Heizung

The code is as follows:

val Functions$Function1 < GenericItem, Float > nvlFloat = [ theItem |
   var Float theValue = 0.0f
   if (theItem.state != NULL) theValue = theItem.getStateAs(DecimalType).floatValue
   theValue
] 
...

         val Float DAR = nvlFloat.apply(I_KG_Heizraum_Heizung_Aussentemperatur) - nvlFloat.apply(I_KG_Heizraum_Keller_TemperaturNormalSoll)
         val Float expectedInletTemp = nvlFloat.apply(I_KG_Heizraum_Keller_TemperaturNormalSoll) + nvlFloat.apply(I_KG_Heizraum_Keller_KennlinieNiveau) 
                           - nvlFloat.apply(I_KG_Heizraum_Keller_KennlinieNeigung) * DAR * (1.4347f + 0.021f * DAR + 0.0002479f * DAR * DAR)

I found out, that the error originates from the terms where a constant float value is multiplied with the Float variable DAR. If I comment out those terms, the error message does not appear:

         val Float DAR = nvlFloat.apply(I_KG_Heizraum_Heizung_Aussentemperatur) - nvlFloat.apply(I_KG_Heizraum_Keller_TemperaturNormalSoll)
         val Float expectedInletTemp = nvlFloat.apply(I_KG_Heizraum_Keller_TemperaturNormalSoll) + nvlFloat.apply(I_KG_Heizraum_Keller_KennlinieNiveau) 
                           - nvlFloat.apply(I_KG_Heizraum_Keller_KennlinieNeigung) * DAR * (1.4347f /*+ 0.021f * DAR + 0.0002479f * DAR * DAR*/)

Strange enough, the multiplication of DAR with the first float variable in the parenthesis works, though.

Can anyone please explain me what is wrong?

Best regards,
Stefan D.

=====================================================

P.S.: I experimented a little bit to come closer to the issue, but I still don’t understand it.
Above calculation does not throw the error when I change the definition of DAR to:

val Float DAR = (nvlFloat.apply(I_KG_Heizraum_Heizung_Aussentemperatur) - nvlFloat.apply(I_KG_Heizraum_FBHZ_TemperaturNormalSoll)).floatValue

So it must be something with return of my function? However, if use the original definition of DAR and change my function to the following, I again get the error:

val Functions$Function1 < GenericItem, Float > nvlFloat = [ theItem |
   var Float theValue = 0.0f
   if (theItem.state != NULL) theValue = theItem.getStateAs(DecimalType).floatValue
   theValue.floatValue
] 

So somehow the subtraction of the two return values of the function seem to do something to the DAR variable to make it incompatible with the multiply function in the next statement…

In Rules DSL by far the most common issue is trying to force the types of the variables. There are only a few rare cases where doing so doesn’t cause more problems than they solve. If you stop doing that, the error generated will likely be more informative.

So, assuming you want a lambda to call to default an Item’s state to 0 if it’s NULL (don’t forget UNDEF too) change it to:

val nvlFloat = [ GenericItem theItem |
    if(theItem.state == NULL || theItem.state == UINDEF) 0 else theItem.state as Number
]

Notice how even with the lambda we don’t muck about with over-specifying types. Specifying theItem as a GenericItem is probably unnecessary here.

Now for your calculations:

    var DAR = nvlFloat.apply(I_KG_Heizraum_Assentemperature) - nvlFloat.apply(I_KG_Heizraum_Keller_TemperaturNormalSoll)

We don’t tell it that DAR is a Float or BigDecimal or Number because it can figure that out at runtime. In fact, even though you’re trying to force it to be a Float, it’s really going to be converted to a BigDecimal on you in the background anyway.

The same goes for the second calculation.

One case that this approach wont handle though is if one or more of these Items is a QuantityType (i.e. has units of measurement). You can tell if the Number Item is defined with a type (e.g. Number:Temperature) or by watching events.log. If you see these Items changing to something like 22.345 °C instead of just 22.345 than you have to deal with the units.

If you are dealing with units, it’s best to keep the units. You can define a constant in Rules DSL with units as follows: 1.23|°C.

One final bit of advice. Break these calculations up. There’s no prize for writing the fewest lines of code and long calculations like this are hard to read, understand, and debug. So convert your Items to Numbers and store those into meaningfully named variables. Then your actual calculation will be much easier to read and understand. And you can log out the converted states of the Items for debugging.

1 Like

As a side note only, sometimes they introduce a performance hit as well. Counter intuitively, a long calculation may take longer to execute than taking interim shorter steps. It’s about how the compiler handles nested calculations, not the actual calculation.
That does usually apply to integer calculations, so probably not relevant here.

Doesn’t matter if adopting “good practice” of breaking up calculations for sanity reasons.

1 Like

Very true, and it’s exacerbated when you over specify the type. And there is a huge performance hit at load time on rules that do this for Rules DSL. I once managed to create a single calculation that took 30 seconds for OH to load and parse on a full multi-core x86 style machine (not an RPi). Breaking it up and avoiding forcing the types dropped that load time down to something basically unmeasurable.

Hi Rich, hi rossko,

Many thanks for your replies!
For a traditional C programmer like me it’s quite counter-intuitive that leaving choice of type to the compiler would actually lead to less problems, but I’ll do my best to get used to it.

However, I’m quite sure that there has been some other problems with not defining the type in my nvl function like proposed by Rich… after all, there must be a reason that I ended up with nvlInt, nvlFloat, and others. But maybe the reason dropped with one of the recent openHab upgrades…

Nevertheless, I would be interested in an explanation why the explicit type definition leads to that error. Any clue? And what instance “null” is actually referred to?

Best,
Stefan D.

At a guess, most likely an inline constant like 0.021f does not yield a Float type.
I think
var xxx = 0.021f
results in a Number, it’s the same operation.

Rules DSL is a weakly typed language. It has way more information about the types of objects at runtime than it does at compile time. It also has a lot more freedom to manipulate the types of objects at runtime than at compile time.

When you force the type, you are forcing the compiler to do a lot more work (I’m guessing it’s O(N!) more work but never looked enough to measure it, it’s at least exponential). Forcing the type even just a handful of times can impose a huge load on the compiler.

In addition, it limits the ability of the runtime interpreter to navigate up and down the type hierarchy to coerce two operands to the right type. You’ve forced it to be a primitive float (e.g. 0.0f) and a primitive float it will forever be. But a primitive float isn’t really compatible with a Float or a Number or BigDecimal class. (Aside: I really have no idea what type var Float theValue = 0.0f will end up being. It could be a Float or it could be a primitive float. But assuming that it does become a Float, what is happening behind the scenes is the primitive is coerced to a BigDecimal which is then cast to a Float (maybe?). It’s all very poorly documented.)

By default, Rules DSL likes numbers to be a BigDecimal. When you give it a number without a type, it converts it to a BigDecimal. If you do a calculation, no matter the types of the operands (assuming it can figure out how to do the operation in the first place) the result will be a BigDecimal. When you force the type, it will first convert the stuff to BigDecimal and sometimes, it fails to to so.

The null error in this case almost always means that the utility function Rules DSL tried to call behind the scenes to coerce the two operands of an operation to types that are compatible with each other and with the operation failed. When doing simple math, that usually means you’re using a primitive as one of the operands and an Object as one of the others.

However, when you don’t force the type, the operands will almost always be some dependent of Number and all operations between Number Objects are compatible.

NOTE: Everything I’ve said above applies to Rules DSL only, not the other supported rules languages which handle types in their own ways.

Rich,
many thanks for your elaborate answer.
Today I finally had the time to fiddle around with it again and I wanted to try your solution to confirm that you explained it all.
Well, I found you did, with every sentence of it. Unfortunately, I still have my little frustration with it as I indeed found out that one of my items is a QuantityType:Temperature while others aren’t, and so I fell into the quantity conversion problem that has been reported in other posts by other users already.
As this is a separate problem, the issue of this thread here is clarified and solved.

NOTE: Everything I’ve said above applies to Rules DSL only, not the other supported rules languages which handle types in their own ways.

I haven’t had the time yet to work into the other rules languages that meanwhile exist. Is this story of types and quantities easier in any of the other languages?

It’s easier in JS Scripting and jRuby because in both of these cases the language is a little better at being typeless than Rules DSL and both’s helper libraries go to great lengths not to mix Java and JS/Ruby. It still comes up sometimes but not as often.

I’ve been working at trying to find a way to help with the QuantityType problem in JS Scripting but time is short and the problem is challenging.

I don’t think it’s much of a problem in Blockly at all.