# How can I optimize this pile of if/then statements?

#### strantor

Joined Oct 3, 2010
5,333
This is copied from plctalk.net, and it is in reference to an Omron PLC program I am writing. I know only a few people here are PLC-aware, but this is textual programming (IEC 61131-3 Structured Text) that should look familiar or at least intelligible to most of you, so maybe you can help me out. The problem is that, as it is written, this function block consumes 989 available steps, and I call on it 4 times in the main program, and I have only 6100 steps total allowable; so this function block *4 leaves me with very little room for the rest of my program.
strantor said:
Hello, I would like to see if anyone can give me some pointers on how to optimize this function block that I have written for a Omron CJ2M. It is just simple piecewise line formula, but the only way I know to handle the piecewise portion is with a slew of if-then statements, which is gobbling up more memory than I can spare. This FB consumes 989 steps and 78 non-retain memory locations! As you can see, I'm not the text-based programming savant. please help!

Code:
(*
This function block takes a bipolar floating point -100 to +100 reference with zero
deadband and converts it to a unipolar integer reference with deadband compensation
and multipoint mapping linearization for a bidirectional analog proportional flow control
valve with nonlinear output.
(EX: 2500 [full reverse] to 7500 [full forward] with deadband [zero speed] from 4000 to 6000)

(*define variables and constants:*)
X1_Neg100:= -100.0;
Y1_NEG100Float:= INT_TO_REAL(NEG100);
X2_Neg80:= -80.0;
Y2_NEG80Float:= INT_TO_REAL(NEG80);
X3_Neg60:= -60.0;
Y3_NEG60Float:= INT_TO_REAL(NEG60);
X4_Neg40:= -40.0;
Y4_NEG40Float:= INT_TO_REAL(NEG40);
X5_Neg20:= -20.0;
Y5_NEG20Float:= INT_TO_REAL(NEG20);
X6_Neg1:= -1.0;
Y6_NEG1Float:= INT_TO_REAL(NEG1);
X7_Pos1:= +1.0;
Y7_POS1Float:= INT_TO_REAL(POS1);
X8_Pos20:= +20.0;
Y8_POS20Float:= INT_TO_REAL(POS20);
X9_Pos40:= +40.0;
Y9_POS40Float:= INT_TO_REAL(POS40);
X10_Pos60:= +60.0;
Y10_POS60Float:= INT_TO_REAL(POS60);
X11_Pos80:= +80.0;
Y11_POS80Float:= INT_TO_REAL(POS80);
X12_Pos100:= +100.0;
Y12_POS100Float:= INT_TO_REAL(POS100);

(*INPUT CONDITIONING - invert incoming value an apply speed limit if speed limit if desired*)
IF Invert = TRUE THEN
IF SpeedLimit = TRUE THEN
InputFloat_X:= Input_float100*(-1.0)*ScalingFactor*(0.5);
ELSE
InputFloat_X:= Input_float100*(-1.0)*ScalingFactor;
END_IF;
ELSE
IF SpeedLimit = TRUE THEN
InputFloat_X:= Input_float100*ScalingFactor*(0.5);
ELSE
InputFloat_X:= Input_float100*ScalingFactor;
END_IF;
END_IF;

(*calculate slope and y-intercept of line segments for positive range and negative range:*)
IF Input_float100 > X11_Pos80 THEN
ValveCenter:= FALSE;
M_Slope:= (Y12_POS100Float-Y11_POS80Float)/(X12_Pos100-X11_Pos80);
Y_Int_B:= Y12_POS100Float-(M_Slope*X12_Pos100);
ELSIF  Input_float100 < X11_Pos80 AND Input_float100 > X10_Pos60 THEN
ValveCenter:= FALSE;
M_Slope:= (Y11_POS80Float-Y10_POS60Float)/(X11_Pos80-X10_Pos60);
Y_Int_B:= Y11_POS80Float-(M_Slope*X11_Pos80);
ELSIF  Input_float100 < X10_Pos60 AND Input_float100 > X9_Pos40 THEN
ValveCenter:= FALSE;
M_Slope:= (Y10_POS60Float-Y9_POS40Float)/(X10_Pos60-X9_Pos40);
Y_Int_B:= Y10_POS60Float-(M_Slope*X10_Pos60);
ELSIF  Input_float100 < X9_Pos40 AND Input_float100 > X8_Pos20 THEN
ValveCenter:= FALSE;
M_Slope:= (Y9_POS40Float-Y8_POS20Float)/(X9_Pos40-X8_Pos20);
Y_Int_B:= Y9_POS40Float-(M_Slope*X9_Pos40);
ELSIF  Input_float100 < X8_Pos20 AND Input_float100 > X7_Pos1 THEN
ValveCenter:= FALSE;
M_Slope:= (Y8_POS20Float-Y7_POS1Float)/(X8_Pos20-X7_Pos1);
Y_Int_B:= Y8_POS20Float-(M_Slope*X8_Pos20);
ELSIF  Input_float100 < X7_Pos1 AND Input_float100 > X6_Neg1 THEN
ValveCenter:= TRUE;
ELSIF  Input_float100 < X6_Neg1 AND Input_float100 > X5_Neg20 THEN
ValveCenter:= FALSE;
M_Slope:= (Y6_NEG1Float-Y5_NEG20Float)/(X6_Neg1-X5_Neg20);
Y_Int_B:= Y6_NEG1Float-(M_Slope*X6_Neg1);
ELSIF  Input_float100 < X5_Neg20 AND Input_float100 > X4_Neg40 THEN
ValveCenter:= FALSE;
M_Slope:= (Y5_NEG20Float-Y4_NEG40Float)/(X5_Neg20-X4_Neg40);
Y_Int_B:= Y5_NEG20Float-(M_Slope*X5_Neg20);
ELSIF  Input_float100 < X4_Neg40 AND Input_float100 > X3_Neg60 THEN
ValveCenter:= FALSE;
M_Slope:= (Y4_NEG40Float-Y3_NEG60Float)/(X4_Neg40-X3_Neg60);
Y_Int_B:= Y4_NEG40Float-(M_Slope*X4_Neg40);
ELSIF  Input_float100 < X3_Neg60 AND Input_float100 > X2_Neg80 THEN
ValveCenter:= FALSE;
M_Slope:= (Y3_NEG60Float-Y2_NEG80Float)/(X3_Neg60-X2_Neg80);
Y_Int_B:= Y3_NEG60Float-(M_Slope*X3_Neg60);
ELSIF  Input_float100 < X2_Neg80  THEN
ValveCenter:= FALSE;
M_Slope:= (Y2_NEG80Float-Y1_NEG100Float)/(X2_Neg80-X1_Neg100);
Y_Int_B:= Y2_NEG80Float-(M_Slope*X2_Neg80);
END_IF;

(*write valve output*)
IF ValveCenter= TRUE THEN
OutputFloat_Y:= +5000.0;
ELSE
OutputFloat_Y:= M_Slope*InputFloat_X+Y_Int_B;
END_IF;
OutputInt:= REAL_TO_INT(OutputFloat_Y);
I see one obvious thing: defining constants like "X2_Neg80:= -80.0;" and then using X2_Neg80 in the code when I could just use -80.0, but I don't think that's the bulk of the problem. I think the problem is deeper and there is an entirely different, better way to write the code from the ground up. I just don't know what that might be.

#### mitko89

Joined Sep 20, 2012
127
Generally if you have statements that follow a pattern you can try to form an expression for a switch statement. The other option is to permorm checks into a for or a while statement (and again use breaks).

#### WBahn

Joined Mar 31, 2012
26,156
How well does a PLC handle array data? If you could define a parallel array that contains the corner points of your calibration curve, then it should be a simple matter of stepping up through the first array to determine the appropriate index and then computing the slope and intercepts in a single part of lines and then apply them. You could also precompute the slopes and intercepts once and store those in the array and then just use them. An even better way might be to store the X breakpoints and the incremental slopes in order to cut down on the amount of data stored.

Here's a simple example.

X is my desired output (in %) and Y is the output (in V, let's say) I need to command to achieve it.

X|Y
-100.0|-12.0
-50.0|1.3
-0.1|3.1
0.1|4.6
50.0|7.9
100.0|15.0

As you can see, this is highly non-linear, is not centered, and has a significant dead band.

Let's say that you want an output of -11.4%. First you identify that this is more than -50% but less than -0.1%, so you know you are working between these two end points.:

-50.0|1.3
-0.1|3.1

Now, you know that you are 40% points higher than the start of this region (-11.4% - -50.0%) = 38.6% and you know that, in this region, the slope from the first point to the second point is

slope = (3.1 V-1.3 V)/(-0.1% - -50.0%) = 36.072 mV/%

So your commanded output needs to be what it is at the beginning of the segment, 1.3 V, and the amount it needs to climb the slop from there, (36.072 mV/%)(38.6%) = 1.392 V, for a total commanded output of 2.692 V.

So how to capture this efficiently in a table look up? Simple, preprocess the slopes to give you a tables that looks like this:

X|Vo|m
-100.0|-12.0|0.266000
-50.0|1.3|0.036072
-0.1|3.1|7.5
0.1|4.6|0.067347
50.0|7.9|0.142000
100.0|15.0|0.000000

Now your code is pretty straight-forward (pseudocode using C-like syntax):

Code:
#define X_MIN (-100.0)
#define X_MAX (100.0)

// Define Lookup Table
#define LOOKUPS (6) // Number of entries in table
double lookup_x[LOOKUPS] = {-100.0, -50.0, -0.1, 0.1, 50.0, 100.0};
double lookup_vo[LOOKUPS] = {-12.0, 1.3, 3.1, 4.6, 7.9, 15.0};
double lookup_vm[LOOKUPS] = {0.266000, 0.036072, 7.5, 0.067347, 0.142000, 0.000000};

x = whatever; // Code to determine the desired output

if (x < X_MIN) x = X_MIN;
if (x > X_MAX) x = X_MAX;

// Find the appropriate index
index = 1;
while ( (index < LOOKUPS) && (x > lookup_x[index]) )
index++;
index--;

v = lookup_vo[index] + (x - lookup_x[index])*lookup_m[index];

#### strantor

Joined Oct 3, 2010
5,333
Thank you for that very informative and detailed reply WBahn, and I apologize for taking so long to reply; I wanted to wait until I had something intelligent to say. Your input has been very helpful and led me on a quest to better understand arrays and loops. Unfortunately the array capabilities of my software are a bit crippled; I cannot specify an input as an array; only internal variables can be arrays - so I have to copy all my endpoints data from an input to the array, which adds more steps and has a diminished effect on optimizing the code. Also, I can find no way to specify the array as you show in one step ("double lookup_x[LOOKUPS] = {-100.0, -50.0, -0.1, 0.1, 50.0, 100.0};"); Apparently I must treat every element of the array as its own entity and issue a separate command to write to it. Also the documentation for my software states that it "only supports one-dimensional arrays" - I'm not quite sure what that means, but I think it means that it is not conducive to establishing a "table". If I'm right about that, and if I'm as clever as I think I am, I think I have gotten around that by using 2 different arrays with corresponding integers so I need only all one integer and reference both arrays with it. Please see below and let me know if there's any more tree I should be barking up.

From plctalk.net:

strantor;614705 said:
Thank you gentlemen for your input. I have combined your ideas and with a few of my own tweaks, I have come up with this (611 steps):
Code:
(*map endpoints to X and Y arrays*)
Y_Value[0]:=NEG100;
Y_Value[1]:=NEG80;
Y_Value[2]:=NEG60;
Y_Value[3]:=NEG40;
Y_Value[4]:=NEG20;
Y_Value[5]:=NEG1;
Y_Value[6]:=POS1;
Y_Value[7]:=POS20;
Y_Value[8]:=POS40;
Y_Value[9]:= POS60;
Y_Value[10]:= POS80;
Y_Value[11]:=POS100;
X_Value[0]:= -100.0;
X_Value[1]:= -80.0;
X_Value[2]:= -60.0;
X_Value[3]:= -40.0;
X_Value[4]:= -20.0;
X_Value[5]:= -1.0;
X_Value[6]:= +1.0;
X_Value[7]:= +20.0;
X_Value[8]:= +40.0;
X_Value[9]:= +60.0;
X_Value[10]:= +80.0;
X_Value[11]:= +100.0;

(*INPUT CONDITIONING - invert incoming value an apply speed limit if speed limit if desired*)
IF SpeedLimit = TRUE THEN
SF:=ScalingFactor*(0.5);
ELSE
SF:=ScalingFactor;
END_IF;
InputFloat_X:= Input_float100*SF;
IF Invert = TRUE then
InputFloat_X:=-InputFloat_X;
END_IF;
(*establish input limits*)
IF InputFloat_X< -100.0 THEN (*negative limit*)
OutputFloat_Y:= INT_TO_REAL(Y_Value[0]);
ELSIF InputFloat_X > +100.0 THEN
OutputFloat_Y:=INT_TO_REAL(Y_Value[11]);(*positive limit*)
ELSIF InputFloat_X < X_Value[6] AND InputFloat_X > X_Value[5] THEN(*center deadband*)
OutputFloat_Y:=+5000.0;
ELSE
(*input range of operation*)
FOR i:= 0 TO 12 BY 1 DO
IF InputFloat_X >= X_Value[i] AND InputFloat_X <= X_Value[i+1] THEN (*evaluate which endpoints the input falls between*)
(*calculate equation of the line between those endpoints*)
M_Slope:=INT_TO_REAL(Y_Value[i]-Y_Value[i+1])/(X_Value[i]-X_Value[i+1]);
Y_Intercept:=INT_TO_REAL(Y_Value[i+1])-(M_Slope*X_Value[i+1]);
OutputFloat_Y:= M_Slope*InputFloat_X+Y_Intercept;
END_IF;
END_FOR;
END_IF;
(*write output*)
OutputInt:= REAL_TO_INT(OutputFloat_Y);
I think this is about as lean as the logic can possibly be (please correct me if I'm wrong). The only further optimization I can see is to eliminate the first section "mapping endpoints." It would be greatly advantageous if I could specify an input as an ARRAY but CX Programmer will only allow internal variables to be specified that way. I am currently investigating using indirect addressing and/or AT commands to achieve it; I know most of the Omron proprietary FBs allow the specification of a "control word," which is the first word, not of an array, but of a series of words that the FB looks at; ex: Control word "n," "n+1," "n+2," etc. - does anyone know how Omron does this? Is it proprietary dark arts? Is there any way for us unwashed to do it as well?

#### NorthGuy

Joined Jun 28, 2014
611
I don't understand why you use PLC. It is enoumously expensive compare to a microcontroller, very bulky, and is extremely difficult to program.

#### WBahn

Joined Mar 31, 2012
26,156
Thank you for that very informative and detailed reply WBahn, and I apologize for taking so long to reply; I wanted to wait until I had something intelligent to say. Your input has been very helpful and led me on a quest to better understand arrays and loops. Unfortunately the array capabilities of my software are a bit crippled; I cannot specify an input as an array; only internal variables can be arrays - so I have to copy all my endpoints data from an input to the array, which adds more steps and has a diminished effect on optimizing the code. Also, I can find no way to specify the array as you show in one step ("double lookup_x[LOOKUPS] = {-100.0, -50.0, -0.1, 0.1, 50.0, 100.0};"); Apparently I must treat every element of the array as its own entity and issue a separate command to write to it. Also the documentation for my software states that it "only supports one-dimensional arrays" - I'm not quite sure what that means, but I think it means that it is not conducive to establishing a "table". If I'm right about that, and if I'm as clever as I think I am, I think I have gotten around that by using 2 different arrays with corresponding integers so I need only all one integer and reference both arrays with it. Please see below and let me know if there's any more tree I should be barking up.

From plctalk.net:
As I presented things, there are three one-dimensional arrays. Since they are intended to be used by having the same index refer into each array, they are known as "parallel arrays", which is basically a means of effectively getting a NxM two-dimensional array using N, 1xM one-dimensional arrays.

#### strantor

Joined Oct 3, 2010
5,333
As I presented things, there are three one-dimensional arrays. Since they are intended to be used by having the same index refer into each array, they are known as "parallel arrays", which is basically a means of effectively getting a NxM two-dimensional array using N, 1xM one-dimensional arrays.
Yes I suppose I made that sound as if I had invented the concept, right after you had clearly illustrated it. I was just reveling in my new-found understanding of it. Thank you again for the help.

#### strantor

Joined Oct 3, 2010
5,333
I don't understand why you use PLC. It is enoumously expensive compare to a microcontroller, very bulky, and is extremely difficult to program.
How can one know the best tool without knowing the application? I need a PLC because:
• I need to interface with common industrial sensors, 24VDC and 120VAC digital I/O, and 0-10V and 4-20mA analog I/O.
• I need to interface with common industrial communications bus/protocols; DeviceNET, J1939 CANbus, and EthernetI/P
• I do not have the resources to design and manufacture circuits to support the above interfaces, or power supply, or any circuits at all.
• Even if I did have the above manufacture capability, I do not have the time or money to submit my microcontroller-based design to all the rigorous testing and global certification required of devices used in my realm (IEC, NEMA, JIS, UL, NK, LR, EC, ISO, et. al.).
• Even if I did have the time and money for the testing and certification above, I don't want the finger pointed at me in the event of a hardware failure.
• I need the flexibility with respect to interfaces that is provided by a PLC; swap out a Pulse I/O module for an analog I/O module, add 5 new digital I/O modules, add a second DeviceNET node, add thermocouple & RTD modules to the remote I/O rack and have them be accessible over DeviceNET, etc.
• I Like PLCs

Money is not high on the list of priorities, nor is space. The only thing that you mention that is really applicable is "extremely difficult to program" - and I would argue that it isn't extremely difficult; in fact in most cases, it's fairly idiot proof. It just takes a bigger idiot (like me) to muck it up. In my case I would say it's only moderately difficult.

#### NorthGuy

Joined Jun 28, 2014
611
Microcontrollers can do all that. There's one inside your PLC. Count in all the time you spent developing programs for PLCs, all the money you spend on purchasing PLCs and modules. This would be more than enough to print and assemble the corresponding number of PCB boards. And I'm 100% sure that if you go with microcntrollers, after some time, you'll like them more

#### WBahn

Joined Mar 31, 2012
26,156
Yes I suppose I made that sound as if I had invented the concept, right after you had clearly illustrated it. I was just reveling in my new-found understanding of it. Thank you again for the help.
Oh, I wasn't complaining about that, just wanted you to see that they were the same thing and also add a bit of vernacular to your vocabulary.

And, to some degree, you probably did reinvent the concept -- that a head slap might be warranted shortly thereafter doesn't necessarily diminish the achievement.

#### WBahn

Joined Mar 31, 2012
26,156
Microcontrollers can do all that. There's one inside your PLC. Count in all the time you spent developing programs for PLCs, all the money you spend on purchasing PLCs and modules. This would be more than enough to print and assemble the corresponding number of PCB boards. And I'm 100% sure that if you go with microcntrollers, after some time, you'll like them more
Don't forget to count in the time getting the code and hardware compatible with all of the needed protocols. Don't forget to count in all the time and cost of getting all of the necessary certification testing performed and past. Don't forget to count in the cost of getting the type of huge liability insurance policy that should be carried.

#### NorthGuy

Joined Jun 28, 2014
611
Don't forget to count in the time getting the code and hardware compatible with all of the needed protocols. Don't forget to count in all the time and cost of getting all of the necessary certification testing performed and past. Don't forget to count in the cost of getting the type of huge liability insurance policy that should be carried.
The standard protocols are common and have been done with microcontrollers many times. You don't need to spend much time/effort on this. I'm not an expert in certifications, and I might be wrong, but I don't think using PLCs somehow alleviates you of any legal liabilities.

#### WBahn

Joined Mar 31, 2012
26,156
The standard protocols are common and have been done with microcontrollers many times. You don't need to spend much time/effort on this. I'm not an expert in certifications, and I might be wrong, but I don't think using PLCs somehow alleviates you of any legal liabilities.
I gives you quite a bit of protection, actually. If the hardware fails then you can point the lawyers at the company that designed and manufactured the hardware (who will typically have deeper pockets than you, anyway). But if you ARE the company that designed and manufactured the hardware.... On top of that, there are many, many projects for which the moment you mention that you plan to use custom, uncertified hardware they will simply tell you not to bother submitting your bid.

#### NorthGuy

Joined Jun 28, 2014
611
On top of that, there are many, many projects for which the moment you mention that you plan to use custom, uncertified hardware they will simply tell you not to bother submitting your bid.
That's how big companies stall the progress and maintain their status quo

#### strantor

Joined Oct 3, 2010
5,333
Microcontrollers can do all that. There's one inside your PLC. Count in all the time you spent developing programs for PLCs, all the money you spend on purchasing PLCs and modules. This would be more than enough to print and assemble the corresponding number of PCB boards. And I'm 100% sure that if you go with microcntrollers, after some time, you'll like them more
I'm not uninitiated to microcontrollers (I've played with atmels and pics, and Like you said, there's one in the PLC) , and I really don't understand your position. From my side of the table the discussion looks like this:
me: "I need some help tracing out the wires to truck's brake lights."
you: "why are you using a truck? They're terribly inefficient, over priced, gaudy looking, and have smelly exhaust. You should use an internal combustion engine."
me: "I would use an internal combusion engine, but then I would have to design a truck up around it, and I don't have time for that, nor do I have time to submit my reinvented wheel to the department of transportation for a certificate of roadworthiness."
you:"well an engine can do anything that a truck can do; in fact, there's an engine in your truck."
me:" yes, you're right, there is. I know what an engine is and have worked on them from time to time, and I really don't understand your position. From my side of the table the discussion looks like this: ... LOOP"

#### panic mode

Joined Oct 10, 2011
1,856

btw. why are you stuck at 6k steps? CJ2M family has range of CPUs supporting up to 60k steps. simply replacing CPU will keep everything else same and you can get more elbow room for rest of the code...

#### NorthGuy

Joined Jun 28, 2014
611
From my side of the table the discussion looks like this
Not like that. The task you want to accomplish can be done in few lines of code on anything that can be programmed in a normal way. You cannot accomplish it with a tool at hands and yet you believe that your tool is the best for the task.

It's like I would see you opening a can of tuna with a mauler and I would mention that a can opener would work better, but you'd tell me back: "I know all about can openers, but I prefer to do it with mauler, and I also need to stand only on my left leg to do this. I love maulers. You see, it's already difficult as it is, and your can opener suggestions don't help me a bit".

Yes, by all means, continue ...

#### NorthGuy

Joined Jun 28, 2014
611
I'll try to suggest something useful.

You can plot your curve in Excel, or other similar tool, and fit it with a polynomial curve. For example, the table from post #2 can be approximated as

y = 9E-06*x^3 - 0.0003*x^2 + 0.0431*x + 4.4673

This looses some precision. If more precision is needed, you can break it into regions and do regions separately.

Such calculalation should not be too hard on the PLC.

#### strantor

Joined Oct 3, 2010
5,333
I'll try to suggest something useful.

You can plot your curve in Excel, or other similar tool, and fit it with a polynomial curve. For example, the table from post #2 can be approximated as

y = 9E-06*x^3 - 0.0003*x^2 + 0.0431*x + 4.4673

This looses some precision. If more precision is needed, you can break it into regions and do regions separately.

Such calculalation should not be too hard on the PLC.
Thank you for the value added post. That was my first instinct as well. I spent almost a whole day fussing around with excel and learning how to use the trend line. I even had a thread here about it. But I tabled that idea for the reason described below (quote from the linked thread):
[...] I've played around with the equation that excel trend line gave me, and got acceptable results. But I've decided to abandon the equation in favor of piecewise scaling. Reason being, the curve can change. The curve is a result of dynamic system parameters; If a mechanical adjustment is made to the valve (or a neighboring valve even), or the valve is replaced, or a new pump or new motor is installed, or fluid of a different viscosity is introduced, etc. etc. ... the curve could become more linear or less linear and/or have different end points. It will need to be field-calibration-friendly, and if I can't figure out how to generate and equation for the curve without consulting a panel of experts, I won't ask the end user to do anything of the sort. I will make a "Valve Output Teach" function, where the user adjusts the raw valve output in increments and observes flow feedback (0-100%) and clicks a " Teach" button at intervals of 20%, 40%, ... 100%.