Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions src/main/java/org/gridsuite/filter/AbstractFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@

import com.fasterxml.jackson.annotation.JsonSubTypes;
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import lombok.*;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
import org.gridsuite.filter.expertfilter.ExpertFilter;
import org.gridsuite.filter.identifierlistfilter.FilterEquipments;
Expand Down Expand Up @@ -43,7 +45,7 @@ public abstract class AbstractFilter implements IFilterAttributes {

private UUID id;

private Date modificationDate;
private Date modificationDate; // TODO use Instant like in servers (client not on same timezone than server)

private EquipmentType equipmentType;

Expand Down
7 changes: 5 additions & 2 deletions src/main/java/org/gridsuite/filter/IFilterAttributes.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,16 @@
import org.gridsuite.filter.utils.EquipmentType;
import org.gridsuite.filter.utils.FilterType;

import java.util.Date;
import java.util.UUID;

/**
* @author Jacques Borsenberger <jacques.borsenberger at rte-france.com>
*/
public interface IFilterAttributes {
java.util.UUID getId();
UUID getId();

java.util.Date getModificationDate();
Date getModificationDate(); // TODO use Instant like in servers (client not on same timezone than server)

FilterType getType();

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package org.gridsuite.filter.globalfilter;

import com.powsybl.iidm.network.Network;
import lombok.NonNull;
import org.gridsuite.filter.AbstractFilter;
import org.gridsuite.filter.FilterLoader;
import org.gridsuite.filter.utils.EquipmentType;

import java.util.List;
import java.util.Objects;
import java.util.UUID;

public abstract class AbstractGlobalFilterService implements FilterLoader {
protected abstract Network getNetwork(@NonNull UUID networkUuid, @NonNull String variantId);

/**
* Get filtered equipment IDs.
* @param networkUuid the network to load
* @param variantId the network variant to work on
* @param globalFilter the filter(s) to apply
* @param equipmentTypes the {@link EquipmentType equipment types} to filter
* @return the {@link List list} of {@link UUID IDs} of filtered {@link EquipmentType equipments}.
*/
protected List<String> getFilteredIds(@NonNull final UUID networkUuid, @NonNull final String variantId,
@NonNull final GlobalFilter globalFilter, @NonNull final List<EquipmentType> equipmentTypes) {
final Network network = getNetwork(networkUuid, variantId);
final List<AbstractFilter> genericFilters = getFilters(globalFilter.getGenericFilter());
return GlobalFilterUtils.applyGlobalFilterOnNetwork(network, globalFilter, genericFilters, equipmentTypes, this)
// Filter equipments by type
.values()
.stream()
.filter(Objects::nonNull)
// Combine all results into one list
.flatMap(List::stream)
.toList();
}
}
47 changes: 47 additions & 0 deletions src/main/java/org/gridsuite/filter/globalfilter/GlobalFilter.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/**
* Copyright (c) 2024, RTE (http://www.rte-france.com)
* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
package org.gridsuite.filter.globalfilter;

import com.powsybl.iidm.network.Country;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.FieldNameConstants;
import lombok.experimental.SuperBuilder;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;

import java.util.List;
import java.util.Map;
import java.util.UUID;

/**
* @author maissa Souissi <maissa.souissi at rte-france.com>
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@SuperBuilder
@FieldNameConstants
// TODO convert to record when loadflow-server and computation lib stop to extends it
public class GlobalFilter {
private List<String> nominalV;
private List<Country> countryCode;
private List<UUID> genericFilter;
private Map<String, List<String>> substationProperty;

/**
* @return {@code true} if all filter parameters are empty, else {@code false}.
*/
public boolean isEmpty() {
return CollectionUtils.isEmpty(this.nominalV)
&& CollectionUtils.isEmpty(this.countryCode)
&& CollectionUtils.isEmpty(this.genericFilter)
&& MapUtils.isEmpty(this.substationProperty)
&& this.substationProperty.values().stream().allMatch(CollectionUtils::isEmpty);
}
}
208 changes: 208 additions & 0 deletions src/main/java/org/gridsuite/filter/globalfilter/GlobalFilterUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
package org.gridsuite.filter.globalfilter;

import com.powsybl.iidm.network.Country;
import com.powsybl.iidm.network.Identifiable;
import com.powsybl.iidm.network.Network;
import org.gridsuite.filter.AbstractFilter;
import org.gridsuite.filter.FilterLoader;
import org.gridsuite.filter.expertfilter.ExpertFilter;
import org.gridsuite.filter.expertfilter.expertrule.*;
import org.gridsuite.filter.utils.EquipmentType;
import org.gridsuite.filter.utils.FiltersUtils;
import org.gridsuite.filter.utils.expertfilter.CombinatorType;
import org.gridsuite.filter.utils.expertfilter.ExpertFilterUtils;
import org.gridsuite.filter.utils.expertfilter.FieldType;
import org.gridsuite.filter.utils.expertfilter.OperatorType;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Predicate;

public final class GlobalFilterUtils {
private GlobalFilterUtils() {
throw new IllegalCallerException("Utility class should not be instantiated");
}

@Nonnull
public static List<FieldType> getNominalVoltageFieldType(@Nonnull final EquipmentType equipmentType) {
return switch (equipmentType) {
case LINE, TWO_WINDINGS_TRANSFORMER -> List.of(FieldType.NOMINAL_VOLTAGE_1, FieldType.NOMINAL_VOLTAGE_2);
case VOLTAGE_LEVEL -> List.of(FieldType.NOMINAL_VOLTAGE);
default -> List.of();
};
}

/**
* Builds nominal voltage rules combining all relevant field types
* @see GlobalFilter#getNominalV()
*/
@Nonnull
public static Optional<AbstractExpertRule> buildNominalVoltageRules(
@Nonnull final List<String> nominalVoltages, @Nonnull final EquipmentType equipmentType) {
final List<FieldType> fields = getNominalVoltageFieldType(equipmentType);
return ExpertFilterUtils.buildOrCombination(nominalVoltages.stream()
.filter(Predicate.not(String::isBlank))
.map(Double::valueOf)
.<AbstractExpertRule>mapMulti((value, accumulator) -> {
for (final FieldType field : fields) {
accumulator.accept(NumberExpertRule.builder()
.value(value)
.field(field)
.operator(OperatorType.EQUALS)
.build());
}
}).toList());
}

@Nonnull
public static List<FieldType> getCountryCodeFieldType(@Nonnull final EquipmentType equipmentType) {
return switch (equipmentType) {
case VOLTAGE_LEVEL, TWO_WINDINGS_TRANSFORMER -> List.of(FieldType.COUNTRY);
case LINE -> List.of(FieldType.COUNTRY_1, FieldType.COUNTRY_2);
default -> List.of();
};
}

/**
* Builds country code rules combining all relevant field types
*/
@Nonnull
public static Optional<AbstractExpertRule> buildCountryCodeRules(
@Nonnull final List<Country> countryCodes, @Nonnull final EquipmentType equipmentType) {
final List<FieldType> fields = getCountryCodeFieldType(equipmentType);
return ExpertFilterUtils.buildOrCombination(countryCodes.stream()
.map(Country::name)
.<AbstractExpertRule>mapMulti((countryCode, accumulator) -> {
for (final FieldType field : fields) {
accumulator.accept(EnumExpertRule.builder()
.value(countryCode)
.field(field)
.operator(OperatorType.EQUALS)
.build());
}
})
.toList());
}

@Nonnull
public static List<FieldType> getSubstationPropertiesFieldTypes(@Nullable final EquipmentType equipmentType) {
return equipmentType == EquipmentType.LINE
? List.of(FieldType.SUBSTATION_PROPERTIES_1, FieldType.SUBSTATION_PROPERTIES_2)
: List.of(FieldType.SUBSTATION_PROPERTIES);
}

/**
* Builds substation property rules combining all relevant field types
*/
@Nonnull
public static Optional<AbstractExpertRule> buildSubstationPropertyRules(
@Nonnull final Map<String, List<String>> properties, @Nonnull final EquipmentType equipmentType) {
final List<FieldType> fields = getSubstationPropertiesFieldTypes(equipmentType);
return ExpertFilterUtils.buildOrCombination(properties.entrySet()
.stream()
.<AbstractExpertRule>mapMulti((entry, accumulator) -> {
for (final FieldType field : fields) {
accumulator.accept(PropertiesExpertRule.builder()
.combinator(CombinatorType.OR)
.operator(OperatorType.IN)
.field(field)
.propertyName(entry.getKey())
.propertyValues(entry.getValue())
.build());
}
})
.toList());
}

/**
* Builds expert filter from a {@link GlobalFilter global filter} for an {@link EquipmentType equipment type}.
*/
@Nullable
public static ExpertFilter buildExpertFilter(@Nonnull final GlobalFilter globalFilter, @Nonnull final EquipmentType equipmentType) {
final List<AbstractExpertRule> andRules = new ArrayList<>();

// Nominal voltage rules
buildNominalVoltageRules(globalFilter.getNominalV(), equipmentType).ifPresent(andRules::add);

// Country code rules
buildCountryCodeRules(globalFilter.getCountryCode(), equipmentType).ifPresent(andRules::add);

// Substation property rules
if (globalFilter.getSubstationProperty() != null) {
buildSubstationPropertyRules(globalFilter.getSubstationProperty(), equipmentType).ifPresent(andRules::add);
}

return andRules.isEmpty() ? null : new ExpertFilter(UUID.randomUUID(), new Date(), equipmentType,
CombinatorExpertRule.builder().combinator(CombinatorType.AND).rules(andRules).build());
}

@Nonnull
private static List<String> filterNetwork(@Nonnull final AbstractFilter filter, @Nonnull final Network network, @Nonnull final FilterLoader filterLoader) {
return FiltersUtils.getIdentifiables(filter, network, filterLoader)
.stream()
.map(Identifiable::getId)
.toList();
}

/**
* Extracts {@link Identifiable#getId() equipment ID}s from a generic filter based on {@link EquipmentType equipment type}.
*/
@Nonnull
public static List<String> applyFilterOnNetwork(@Nonnull final AbstractFilter filter, @Nonnull final EquipmentType targetEquipmentType,
@Nonnull final Network network, @Nonnull final FilterLoader filterLoader) {
if (filter.getEquipmentType() == targetEquipmentType) {
return filterNetwork(filter, network, filterLoader);
} else if (filter.getEquipmentType() == EquipmentType.VOLTAGE_LEVEL) {
return filterNetwork(ExpertFilterUtils.buildExpertFilterWithVoltageLevelIdsCriteria(filter.getId(), targetEquipmentType), network, filterLoader);
}
return List.of();
}

/**
* Extracts filtered {@link Identifiable#getId() equipment ID}s by applying {@link ExpertFilter expert}
* and {@link AbstractFilter generic filter}s.
*/
@Nonnull
public static List<String> applyGlobalFilterOnNetwork(@Nonnull final Network network,
@Nonnull final GlobalFilter globalFilter, @Nonnull final List<AbstractFilter> genericFilters,
@Nonnull final EquipmentType equipmentType, @Nonnull final FilterLoader filterLoader) {
List<List<String>> allFilterResults = new ArrayList<>(1 + genericFilters.size());

// Extract IDs from expert filter
final ExpertFilter expertFilter = buildExpertFilter(globalFilter, equipmentType);
if (expertFilter != null) {
allFilterResults.add(filterNetwork(expertFilter, network, filterLoader));
}

// Extract IDs from generic filters
for (final AbstractFilter filter : genericFilters) {
final List<String> filterResult = applyFilterOnNetwork(filter, equipmentType, network, filterLoader);
if (!filterResult.isEmpty()) {
allFilterResults.add(filterResult);
}
}

// Combine results with appropriate logic
// Expert filters use OR between them, generic filters use AND
return FiltersUtils.combineFilterResults(allFilterResults, !genericFilters.isEmpty());
}

/**
* Filters equipments by {@link EquipmentType type}
* @return map of {@link Identifiable#getId() equipment ID}s grouped by {@link EquipmentType equipment type}
*/
@Nonnull
public static Map<EquipmentType, List<String>> applyGlobalFilterOnNetwork(@Nonnull final Network network,
@Nonnull final GlobalFilter globalFilter, @Nonnull final List<AbstractFilter> genericFilters,
@Nonnull final List<EquipmentType> equipmentTypes, @Nonnull final FilterLoader filterLoader) {
Map<EquipmentType, List<String>> result = new EnumMap<>(EquipmentType.class);
for (final EquipmentType equipmentType : equipmentTypes) {
final List<String> filteredIds = applyGlobalFilterOnNetwork(network, globalFilter, genericFilters, equipmentType, filterLoader);
if (!filteredIds.isEmpty()) {
result.put(equipmentType, filteredIds);
}
}
return result;
}
}
28 changes: 28 additions & 0 deletions src/main/java/org/gridsuite/filter/utils/FiltersUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
import org.gridsuite.filter.identifierlistfilter.IdentifierListFilter;
import org.gridsuite.filter.identifierlistfilter.IdentifierListFilterEquipmentAttributes;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Stream;
Expand Down Expand Up @@ -237,4 +239,30 @@ public static List<Identifiable<?>> getIdentifiables(AbstractFilter filter, Netw
case SUBSTATION -> getSubstationList(network, filter, filterLoader);
};
}

/**
* Combines multiple filter results using AND or OR logic.
*/
@Nonnull
public static <E> List<E> combineFilterResults(@Nullable final List<List<E>> filterResults, final boolean useAndLogic) {
if (filterResults == null || filterResults.isEmpty()) {
return List.of();
}
if (filterResults.size() == 1) {
return filterResults.getFirst();
}
if (useAndLogic) {
// Intersection of all results
Set<E> result = new HashSet<>(filterResults.getFirst());
for (int i = 1; i < filterResults.size(); i++) {
result.retainAll(filterResults.get(i));
}
return new ArrayList<>(result);
} else {
// Union of all results
Set<E> result = new HashSet<>();
filterResults.forEach(result::addAll);
return new ArrayList<>(result);
}
}
}
24 changes: 24 additions & 0 deletions src/main/java/org/gridsuite/filter/utils/TimeUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package org.gridsuite.filter.utils;

import com.google.common.annotations.VisibleForTesting;
import lombok.Setter;

import java.util.UUID;
import java.util.function.Supplier;

/**
* Utility class for UUID to permit during tests to mock {@link UUID} generation..
*/
@VisibleForTesting
public final class TimeUtils {
private TimeUtils() {
throw new AssertionError("Utility class should not be instantiated");
}

@Setter
private static Supplier<UUID> uuidSupplier = UUID::randomUUID;

public static UUID generateUUID() {
return uuidSupplier.get();
}
}
Loading