FAQ: How can I resolve the error due to lookup table not allowing repeated breakpoints?

32 views (last 30 days)
I'm trying to model abrupt discontinuities using lookup tables.
For example, consider this data that has abrupt discontinuities for input values 3 and 5.
breakpoints = [0 3 3 3 5 5 7]
breakpoints = 1×7
0 3 3 3 5 5 7
tableData = [0 9 19 29 45 35 79]
tableData = 1×7
0 9 19 29 45 35 79
plot(breakpoints,tableData)
If I try to use this with Simulink Lookup Table blocks or MATLAB's interp1, I get errors complaining about the breakpoints not being monotonically increasing.
For example, this code produces an error
interp1(breakpoints,tableData,2)
Error using matlab.internal.math.interp1
Sample points must be unique.

Error in interp1 (line 188)
VqLite = matlab.internal.math.interp1(X,V,method,method,Xqcol);
How can I resolve this error and model the discontinuities with lookup tables?

Accepted Answer

Andy Bartlett
Andy Bartlett on 5 Jul 2023
Edited: Andy Bartlett on 6 Jul 2023
A solution for making the lookup tables happy is to move the repeated breakpoints a "tiny amount".
A key basis for this approach is the fact that the data type used for the lookup table input and corresponding breakpoint will always be discrete.
Even a luxuriously fine grained data type like doubles is made up of a sequence of discrete representable values.
For example, in double we have ... 3-eps(3), 3, 3+eps(3) ... There are no representable values in the type double that are between 3 and 3+eps(3). In the software, the input range is NOT a continuum, so infinitely fast discontinuities don't really occur in the software in discrete data types.
So, each discontinuous area can just be modeled over its set of representable values.
To solve the repeated breakpoint problem, we can split up repeated breakpoints by moving one of them to the nearest neighboring representable values.
The attached function eliminateBreakpointRepeats makes those tiny moves.
Example
breakpoints = [0 3 3 3 5 5 7];
tableData = [0 9 19 29 45 35 79];
breakpointsNew = eliminateBreakpointRepeats(breakpoints);
mat2str(breakpointsNew,17)
ans = '[0 2.9999999999999996 3 3.0000000000000004 4.9999999999999991 5 7]'
plot(breakpoints,tableData,'b-',breakpoints,tableData,'bo',...
breakpointsNew,tableData,'k-',breakpointsNew,tableData,'kx')
xlabel('input')
ylabel('output')
legend({'Original curve'
'Original table points'
'New curve'
'New table points'},'location','NorthWest')
interp1(breakpointsNew,tableData,2)
ans = 6.0000
As we see the previous error is resolved.
However, this was a simple default case where everything was using double.
Types Used by Lookup MUST be Considered
Many MathWorks users take their designs all the way to deployment on embedded real-time chips. In these cases, the data types used by lookup tables are almost never the luxurious doubles. Instead, they are the more efficient singles or the typically much more efficient fixed-point types.
Simulink Lookup Table blocks require breakpoints to be strictly monotonically increasing in the data type used for the breakpoint.
Separating repeating breakpoints in doubles would still collide as repeated breakpoints in a coarser data type like singles or most fixed-point types. So it is critical for elimination of repeats to consider the type the lookup will actually use.
The attached function eliminateBreakpointRepeats allows the data type to be used to specified and will achieve separation of breakpoints in the specified type.
Example 0
breakpointsOrig0 = [0 3 3 3 5 5 7];
breakpoints0 = eliminateBreakpointRepeats(breakpointsOrig0);
mat2str(breakpointsOrig0,17,'class')
ans = 'double([0 3 3 3 5 5 7])'
mat2str(breakpoints0,17,'class')
ans = 'double([0 2.9999999999999996 3 3.0000000000000004 4.9999999999999991 5 7])'
Example 1
breakpointsOrig1 = [0 3 3 3 5 5 7];
data_type_specification1 = single(1); % specify type with numeric value
[breakpoints1,bpt1] = eliminateBreakpointRepeats(breakpointsOrig1,data_type_specification1);
mat2str(breakpointsOrig1,17,'class')
ans = 'double([0 3 3 3 5 5 7])'
mat2str(breakpoints1,17,'class')
ans = 'double([0 2.9999997615814209 3 3.0000002384185791 4.9999995231628418 5 7])'
mat2str(bpt1,17,'class')
ans = 'single([0 2.9999997615814209 3 3.0000002384185791 4.9999995231628418 5 7])'
Example 2
breakpointsOrig2 = [0 3 3 3 5 5 7];
data_type_specification2 = 'half'; % specify type by name
[breakpoints2,bpt2] = eliminateBreakpointRepeats(breakpointsOrig2,data_type_specification2);
mat2str(breakpointsOrig2,17,'class')
ans = 'double([0 3 3 3 5 5 7])'
mat2str(breakpoints2,17,'class')
ans = 'double([0 2.998046875 3 3.001953125 4.99609375 5 7])'
mat2str(bpt2,17,'class')
ans = 'half([0 2.998046875 3 3.001953125 4.99609375 5 7])'
Example 3
breakpointsOrig3 = [0 3 3 3 5 5 7];
data_type_specification3 = numerictype(0,8,5); % specify type by object
[breakpoints3,bpt3] = eliminateBreakpointRepeats(breakpointsOrig3,data_type_specification3);
mat2str(breakpointsOrig3,17,'class')
ans = 'double([0 3 3 3 5 5 7])'
mat2str(breakpoints3,17,'class')
ans = 'double([0 2.96875 3 3.03125 4.96875 5 7])'
bpt3
bpt3 =
0 2.9688 3.0000 3.0312 4.9688 5.0000 7.0000 DataTypeMode: Fixed-point: binary point scaling Signedness: Unsigned WordLength: 8 FractionLength: 5
Corresponding Changes in Table Values if Big Movement in Breakpoints
If the change in breakpoints is "sufficiently small", then the table data doesn't need to be changed.
But if the changes are "large" due to nearest representable values being "far away", then the lookup my be distorted from the original.
As an example of noticeable distortion, consider the original example, but with fixed-point type with only one bit to the right of the binary point. This type represents value are 0, 0.5, 1, 1.5, ... 127.5, so the breakpoint changes are fairly large.
The large breakpoint changes lead to noticable distortion, especially for input values 3.5 and 4.5.
breakpoints = [0 3 3 3 5 5 7];
tableData = [0 9 19 29 45 35 79];
nt = numerictype(0,8,1);
breakpointsNew = eliminateBreakpointRepeats(breakpoints,nt);
mat2str(breakpointsNew,17)
ans = '[0 2.5 3 3.5 4.5 5 7]'
u = 0:0.5:7;
y = interp1(breakpointsNew,tableData,u);
plot(breakpoints,tableData,'b-',breakpoints,tableData,'bo',...
u,y,'kx',breakpointsNew,tableData,'rd')
xlabel('input')
ylabel('output')
legend({'Original curve'
'Original table points'
'New curve for representable inputs'
'Orig Table Data at New Breakpoints'},'location','NorthWest')
To avoid the distortion, the table data needs to change also.
tableDataNew = tableData
tableDataNew = 1×7
0 9 19 29 45 35 79
% 2nd breakpoint 3 moved to 2.5
iBreak = 2;
iiData = 1:2;
tableDataNew(iBreak) = interp1(breakpoints(iiData),tableData(iiData),breakpointsNew(iBreak));
% 4th breakpoint 3 moved to 3.5
iBreak = 4;
iiData = 4:5;
tableDataNew(iBreak) = interp1(breakpoints(iiData),tableData(iiData),breakpointsNew(iBreak));
% 5th breakpoint 4 moved to 4.5
iBreak = 5;
iiData = 4:5;
tableDataNew(iBreak) = interp1(breakpoints(iiData),tableData(iiData),breakpointsNew(iBreak));
u = 0:0.5:7;
yCorrected = interp1(breakpointsNew,tableDataNew,u);
plot(breakpoints,tableData,'b-',breakpoints,tableData,'bo',...
u,yCorrected,'kx',breakpointsNew,tableDataNew,'md')
xlabel('input')
ylabel('output')
legend({'Original curve'
'Original table points'
'New curve for representable inputs'
'New table points distortion corrected'},'location','NorthWest')
By adjusting the table data to agree with the new breakpoints, all the distortion was removed.
Also, notice that we're only judging the distortion for the representable inputs values (x's). There are no representable values between the x's, so distortion there is meaningless.
Critical Assumption
A critical assumption for this approach was that the lookup table is using the same data type for a breakpoint as its corresponding input, which is the default for Simulink Lookup Table blocks.
If a coarser data type was used for the breakpoints, then the input would have representable values that are between representable values in the breakpoint data type. So if you need to model discontinuities, then to avoid these issue use the default of having breakpoint and corresponding inputs use identical data types.
Summary
One way to resolve errors due to repeated breakpoints is to slightly move the breakpoints.
Simply determine the data type that will be used by the lookup table and call the attached function eliminateBreakpointRepeats. See help eliminateBreakpointRepeats for more details on usage.
If the breakpoints move a lot, then you may need to change the table data to avoid distortion.
But if you are using doubles, or even singles, then distortion is likely negligible and you can leave the table data unchanged.

More Answers (0)

Community Treasure Hunt

Find the treasures in MATLAB Central and discover how the community can help you!

Start Hunting!