Skip to content

Commit 832e0e3

Browse files
dynamic rounding of results for comparisons (#151)
Signed-off-by: Mathieu DEHARBE <[email protected]>
1 parent 265a7a8 commit 832e0e3

File tree

3 files changed

+39
-8
lines changed

3 files changed

+39
-8
lines changed

src/main/java/org/gridsuite/securityanalysis/server/dto/ResourceFilterDTO.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,9 +15,13 @@
1515
* @param type the type of filter (contains, startsWith...)
1616
* @param value the value of the filter
1717
* @param column the column / field on which the filter will be applied
18+
* @param tolerance precision/tolerance used for the comparisons (simulates the rounding of the database values) Only useful for numbers.
1819
* @author Kevin Le Saulnier <kevin.lesaulnier at rte-france.com>
1920
*/
20-
public record ResourceFilterDTO(DataType dataType, Type type, Object value, String column) {
21+
public record ResourceFilterDTO(DataType dataType, Type type, Object value, String column, Double tolerance) {
22+
public ResourceFilterDTO(DataType dataType, Type type, Object value, String column) {
23+
this(dataType, type, value, column, null);
24+
}
2125

2226
public enum DataType {
2327
@JsonProperty("text")

src/main/java/org/gridsuite/securityanalysis/server/repositories/specifications/SpecificationUtils.java

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,17 @@ public static <X> Specification<X> startsWith(String field, String value) {
5252
return (root, cq, cb) -> cb.like(cb.upper(getColumnPath(root, field)), EscapeCharacter.DEFAULT.escape(value).toUpperCase() + "%", EscapeCharacter.DEFAULT.getEscapeCharacter());
5353
}
5454

55+
/**
56+
* Returns a specification where the field value is not equal within the given tolerance.
57+
*/
5558
public static <X> Specification<X> notEqual(String field, Double value, Double tolerance) {
5659
return (root, cq, cb) -> {
5760
Expression<Double> doubleExpression = getColumnPath(root, field).as(Double.class);
61+
/**
62+
* in order to be equal to doubleExpression, value has to fit :
63+
* value - tolerance <= doubleExpression <= value + tolerance
64+
* therefore in order to be different at least one of the opposite comparison needs to be true :
65+
*/
5866
return cb.or(
5967
cb.greaterThan(doubleExpression, value + tolerance),
6068
cb.lessThan(doubleExpression, value - tolerance)
@@ -137,13 +145,27 @@ private static <X> Specification<X> appendTextFilterToSpecification(Specificatio
137145

138146
@NotNull
139147
private static <X> Specification<X> appendNumberFilterToSpecification(Specification<X> specification, ResourceFilterDTO resourceFilter) {
140-
final double tolerance = 0.00001; // tolerance for comparison
141148
String value = resourceFilter.value().toString();
142-
return createNumberPredicate(specification, resourceFilter, value, tolerance);
149+
return createNumberPredicate(specification, resourceFilter, value);
143150
}
144151

145-
private static <X> Specification<X> createNumberPredicate(Specification<X> specification, ResourceFilterDTO resourceFilter, String value, double tolerance) {
146-
Double valueDouble = Double.valueOf(value);
152+
private static <X> Specification<X> createNumberPredicate(Specification<X> specification, ResourceFilterDTO resourceFilter, String filterValue) {
153+
double tolerance;
154+
if (resourceFilter.tolerance() != null) {
155+
tolerance = resourceFilter.tolerance();
156+
} else {
157+
// the reference for the comparison is the number of digits after the decimal point in filterValue
158+
// extra digits are ignored, but the user may add '0's after the decimal point in order to get a better precision
159+
String[] splitValue = filterValue.split("\\.");
160+
int numberOfDecimalAfterDot = 0;
161+
if (splitValue.length > 1) {
162+
numberOfDecimalAfterDot = splitValue[1].length();
163+
}
164+
// tolerance is multiplied by 0.5 to simulate the fact that the database value is rounded (in the front, from the user viewpoint)
165+
// more than 13 decimal after dot will likely cause rounding errors due to double precision
166+
tolerance = Math.pow(10, -numberOfDecimalAfterDot) * 0.5;
167+
}
168+
Double valueDouble = Double.valueOf(filterValue);
147169
return switch (resourceFilter.type()) {
148170
case NOT_EQUAL -> specification.and(notEqual(resourceFilter.column(), valueDouble, tolerance));
149171
case LESS_THAN_OR_EQUAL ->

src/test/java/org/gridsuite/securityanalysis/server/FindPreContingencyLimitViolationTest.java

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,10 @@ void findFilteredPrecontingencyLimitViolationResultsTest(List<ResourceFilterDTO>
7575
List<PreContingencyLimitViolationResultDTO> preContingencyLimitViolation = securityAnalysisResultService.findNResult(resultEntity.getId(), filters, sort);
7676

7777
// assert subject ids to check parent filters
78-
assertThat(preContingencyLimitViolation).extracting(SubjectLimitViolationEntity.Fields.subjectId).containsExactlyElementsOf(expectedResult.stream().map(PreContingencyLimitViolationResultDTO::getSubjectId).toList());
78+
assertThat(preContingencyLimitViolation).extracting(SubjectLimitViolationEntity.Fields.subjectId)
79+
.containsExactlyElementsOf(
80+
expectedResult.stream().map(PreContingencyLimitViolationResultDTO::getSubjectId).toList()
81+
);
7982
assertSelectCount(expectedSelectCount);
8083
}
8184

@@ -110,9 +113,11 @@ private static Stream<Arguments> provideChildFilter() {
110113

111114
private static Stream<Arguments> provideChildFilterWithTolerance() {
112115
return Stream.of(
113-
Arguments.of(List.of(new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.NOT_EQUAL, "11.02425", AbstractLimitViolationEntity.Fields.value),
116+
Arguments.of(List.of(
117+
new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.NOT_EQUAL, "11.02425", AbstractLimitViolationEntity.Fields.value),
114118
new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.LESS_THAN_OR_EQUAL, "10.51243", AbstractLimitViolationEntity.Fields.limit),
115-
new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.GREATER_THAN_OR_EQUAL, "1.00001", AbstractLimitViolationEntity.Fields.limitReduction)), Sort.by(Sort.Direction.ASC, "limit"),
119+
new ResourceFilterDTO(ResourceFilterDTO.DataType.NUMBER, ResourceFilterDTO.Type.GREATER_THAN_OR_EQUAL, "0.999999", AbstractLimitViolationEntity.Fields.limitReduction)
120+
), Sort.by(Sort.Direction.ASC, "limit"),
116121
getResultPreContingencyWithNestedFilter(p -> p.getLimitViolation().getLimit() <= 10.51243)
117122
.stream().sorted(Comparator.comparing(x -> x.getLimitViolation().getLimit())).toList(), 2)
118123
);

0 commit comments

Comments
 (0)