Conditional Scripting/Expressions

Hi guys, looking for opinions if you’d be so kind! I’m trying to figure out the best way to write an expression that would produce a string value based on the following dataset of conditions:
image

Based on the conditions from the first four columns on the left, I want to produce the “Selected Option” displayed in the same row on the far right.

I started on a huge string of if statements but the complexity of the “ifs” is leading me to believe there is much simpler way to do this that I just haven’t discovered yet.

What would you guys do?

A case statement would be easier than going with ifs.

Edit: Actually that might not work for your use.

When I set up scenarios for similar situations I have a somewhat complicated solution I fall back to because it works.

It looks like you have the wonderful luck of having four columns with binary values (0/1, and “Good”/“Bad”). Mentally convert all of those values to actual binary bits:

0 -> 0
1 -> 1
"Bad" -> 0
"Good" -> 1

Now you can represent each row as a four-bit binary value, ie row 0 becomes 0101.

Now, instead of having to do conditionals to determine the what to do for each branch you can indeed fall back to a switch statement, or you could just use a Python dictionary:

tags = system.tag.readBlocking(["pathOne.value", "pathOne.quality", "pathTwo.value", "pathTwo.quality"])
opt_1_val = tags[0]
opt_1_qual = tags[1]
opt_2_val = tags[2]
opt_2_qual = tags[3]
converted = "".join([str(opt_1_val), "1" if opt_1_qual == "Good" else "0", str(opt_2_val), "1" if opt_2_qual == "Good" else "0"])
value_map = {
    "0101": "Option 1",
    "1111": "Option 2",
    "0110": "Option 1"
}  # and include the rest of the valid options
return value_map.get(converted, None)  # None if map contains no match

The downside is the map requires maintenance when requirements change (like added return values or changes to what each four-bit value represents), but it prevents long, branching if statements.

1 Like

I would move the boolean logic into here instead of into the join. I think you also still need the .value as well, so I would instead just read the 2 tags:

from com.inductiveautomation.ignition.common.model.values import QualityCode
tags = system.tag.readBlocking(["pathOne", "pathTwo"])
options = []
for tag in tags:
   options.append(tag.value)
   options.append(tag.quality.level == QualityCode.Level.Good))

converted = "".join({True: '1', False: '0'}.get(option, '0') for option in options)

Maybe using QualityCode here is overkill...

Great thinking! I’m going to give that a shot today. Thanks so much!

Very cool idea! I will look into this today as well! Thanks @nminchin !

I would just use

options.append(tag.quality.good)
1 Like

@josborn I looked at using a case statement as well but it seemed equally as cumbersome as the if statement scenario! Thanks for the reply!

A switch statement is actually a lot cleaner than even the map because you can fall through similar cases, so you’d actually only need four or five definitions.

# not perfect syntax, but the gist is this:
switch (converted): {
    case "0101":
    case "0110":
    case "0111":
    case "1001":
        return "Option 1"
    case "1111":
    case "0011":
    case "1101":
    case "1100":
        return "Option 2"
    case "0011":
    case "1000":
        return "Lad Good Option"
    default:
        return "option not mapped"
}

Edit: I’m not really sure the correct Jython syntax. Python doesn’t support switch statements - they’re a Java concept - but Jython doesn’t require semi-colons, so… maybe this will work as-is? And since we’re returning we don’t need break.

1 Like

@cmallonee Nice!!

No, it won't. The pythonic structure is if-elif-elif-else.

You could always use a dictionary with a .get() but since there’s so many cases with the same output I think the easiest way would be

# assume we have converted variable that is the 4 digit string
case_1 = converted in ["0101", "0110", "0111", "1001"]
case_2 = converted in ["1111","0011","1101","1100"]
case_3 = converted in ["0011","1000"]

if case_1:
    return "option 1"
elif case_2:
    return "option 2";
elif case_3:
    return "option 3"
else:
    return 'option not mapped'

Ah, so Jython doesn’t support switch statements at all? That’s too bad.

The python equivalent to switch statements, Match statements, were only just introduced to python 3.10 IIRC

Here’s a link to all of the alternatives we’ve covered here. As @bkarabinchak.psi pointed out, the match case is not available in Ignition because we’re not using Python 3.10.

I would not compute case_1, case_2, and case_3 ahead of the if-elif-elif-else block. Compute in the if block. Put the most likely condition first. The other computations will be skipped.

I was trying to put all of this in an expression tag, but haven’t had luck making it work yet with all of your great suggestions… Should I maybe be trying to place all of this somewhere else? Would all of this be better suited as a script instead of an expression?

All of the examples given so far use scripting. You could use binEnc and case to do something similar entirely with expressions, but the logic becomes (arguably) more difficult to reason about:


case(
	binEnc(
		{Tag1},
		isBad({Tag1}),
		{Tag2},
		isBad({Tag2})
	),
	0, "Option 1", // 0000
	1, "Option 2", // 0001
	2, "Option 1",
	"Fallback"
)
1 Like

I would probably put the binEnc into another tag, so that you can see the intermediate value.

1 Like