Skip to content
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
/**
* Copyright (c) 2026, 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 com.powsybl.dynawo.suppliers.dynamicmodels;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.module.SimpleModule;
import com.powsybl.commons.json.JsonUtil;

import java.util.List;

/**
* @author Thang PHAM <quyet-thang.pham at rte-france.com>
*/
public final class DynamicModelConfigJsonUtils {

private DynamicModelConfigJsonUtils() {
throw new AssertionError("Utility class should not be instantiated");
}

public static ObjectMapper createObjectMapper() {
ObjectMapper mapper = JsonUtil.createObjectMapper();

SimpleModule module = new SimpleModule("dynamic-model-configs");
module.addSerializer(new DynamicModelConfigsJsonSerializer());
module.addDeserializer(List.class, new DynamicModelConfigsJsonDeserializer());

mapper.registerModule(module);
return mapper;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/**
* Copyright (c) 2026, 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 com.powsybl.dynawo.suppliers.dynamicmodels;

import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.databind.SerializerProvider;
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
import com.powsybl.dynawo.suppliers.Property;
import com.powsybl.dynawo.suppliers.PropertyType;
import com.powsybl.iidm.network.TwoSides;

import java.io.IOException;
import java.util.List;

/**
* Serialize a List<DynamicModelConfig> with "models" as root:
*
* {
* "models": [
* {
* "model": "...",
* "group": "...",
* "groupType": "FIXED",
* "properties": [ ... ]
* }
* ]
* }
*
* This matches {@link DynamicModelConfigsJsonDeserializer}.
*
* TODO : to remove when available at powsybl-dynawo
*
* @author Thang PHAM <quyet-thang.pham at rte-france.com>
*/
public class DynamicModelConfigsJsonSerializer extends StdSerializer<List<DynamicModelConfig>> {

public DynamicModelConfigsJsonSerializer() {
super((Class<List<DynamicModelConfig>>) (Class<?>) List.class);
}

@Override
public void serialize(List<DynamicModelConfig> configs,
JsonGenerator gen,
SerializerProvider provider) throws IOException {

gen.writeStartObject();

gen.writeFieldName("models");
writeDynamicModelConfigs(configs, gen);

gen.writeEndObject();
}

private static void writeDynamicModelConfigs(List<DynamicModelConfig> configs, JsonGenerator gen) throws IOException {
gen.writeStartArray();
if (configs != null) {
for (DynamicModelConfig cfg : configs) {
writeDynamicModelConfig(cfg, gen);
}
}
gen.writeEndArray();
}

private static void writeDynamicModelConfig(DynamicModelConfig cfg,
JsonGenerator gen) throws IOException {

gen.writeStartObject();

gen.writeStringField("model", cfg.model());
gen.writeStringField("group", cfg.group());

if (cfg.groupType() != null) {
gen.writeStringField("groupType", cfg.groupType().name());
}

gen.writeFieldName("properties");
writeProperties(cfg.properties(), gen);

gen.writeEndObject();
}

private static void writeProperties(List<Property> properties, JsonGenerator gen) throws IOException {
gen.writeStartArray();
if (properties != null) {
for (Property p : properties) {
writeProperty(p, gen);
}
}
gen.writeEndArray();
}

/**
* See {@code PropertyParserUtils.parseProperty}:
* - always writes "name"
* - writes exactly one of: "value" (string value), "values" (string array), "arrays" (array of string arrays)
* - writes "type" when it can be inferred from propertyClass
*/
private static void writeProperty(Property property, JsonGenerator gen) throws IOException {
gen.writeStartObject();

gen.writeStringField("name", property.name());

Object value = property.value();
if (value instanceof List<?> list) {
if (!list.isEmpty()) {
// lists: List<String|int|double|boolean|TwoSides>
gen.writeFieldName("values");
gen.writeStartArray();
for (Object v : list) {
gen.writeString(String.valueOf(v));
}
gen.writeEndArray();
writeOptionalType(gen, property);
}
} else if (value != null && value.getClass().isArray()) {
// arrays: List<List<String|integer|double|boolean|TwoSides>>
gen.writeFieldName("arrays");
gen.writeStartArray();
for (List<?> row : (List<?>[]) value) {
gen.writeStartArray();
for (Object v : row) {
gen.writeString(String.valueOf(v));
}
gen.writeEndArray();
}
gen.writeEndArray();
writeOptionalType(gen, property);
} else {
if (value instanceof TwoSides ts) {
gen.writeStringField("value", ts.name());
writeOptionalType(gen, property);
} else if (value != null) {
gen.writeStringField("value", String.valueOf(value));
writeOptionalType(gen, property);
}
}

gen.writeEndObject();
}

/**
* Writes the "type" field if we can (or if Property already carries an explicit type via propertyClass()).
*
* This keeps compatibility with PropertyParserUtils:
* case "type" -> builder.type(PropertyType.valueOf(parser.nextTextValue()));
*/
private static void writeOptionalType(JsonGenerator gen, Property property) throws IOException {
PropertyType inferredType = PropertyType.STRING;

Class<?> propertyClass = property.propertyClass();
if (propertyClass != null) {
if (propertyClass == double.class) {
inferredType = PropertyType.DOUBLE;
} else if (propertyClass == int.class) {
inferredType = PropertyType.INTEGER;
} else if (propertyClass == boolean.class) {
inferredType = PropertyType.BOOLEAN;
} else if (propertyClass == TwoSides.class) {
inferredType = PropertyType.TWO_SIDES;
}
}

gen.writeStringField("type", inferredType.name());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ public ResponseEntity<Resource> downloadDebugFile(@Parameter(description = "Resu
public ResponseEntity<List<DynamicModelConfig>> exportDynamicModel(@PathVariable("networkUuid") UUID networkUuid,
@RequestParam(name = "variantId", required = false) String variantId,
@RequestParam(name = "mappingName") String mappingName) {
List<DynamicModelConfig> dynamicModelConfigList = dynamicSimulationService.exportDynamicModel(networkUuid, variantId, mappingName);
List<DynamicModelConfig> dynamicModelConfigList = parametersService.getDynamicModel(mappingName, networkUuid, variantId);
return CollectionUtils.isNotEmpty(dynamicModelConfigList) ?
ResponseEntity.ok().contentType(MediaType.APPLICATION_JSON).body(dynamicModelConfigList) :
ResponseEntity.noContent().build();
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* Copyright (c) 2026, 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.ds.server.controller;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.gridsuite.ds.server.DynamicSimulationApi;
import org.gridsuite.ds.server.dto.DynamicSimulationParametersInfos;
import org.gridsuite.ds.server.dto.DynamicSimulationParametersValues;
import org.gridsuite.ds.server.service.parameters.ParametersService;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.Optional;
import java.util.UUID;


/**
* @author Thang PHAM <quyet-thang.pham at rte-france.com>
*/
@RestController
@RequestMapping(value = "/" + DynamicSimulationApi.API_VERSION + "/parameters")
@Tag(name = "Dynamic simulation server - Parameters")
public class DynamicSimulationParametersController {

private final ParametersService parametersService;

public DynamicSimulationParametersController(ParametersService parametersService) {
this.parametersService = parametersService;
}

@PostMapping(value = "/values", produces = MediaType.APPLICATION_JSON_VALUE)
@Operation(summary = "Get the dynamic simulation parameters values")
@ApiResponses(value = {@ApiResponse(responseCode = "200", description = "The dynamic simulation parameters values"),
@ApiResponse(responseCode = "404", description = "The dynamic simulation parameters has not been found")})
public ResponseEntity<DynamicSimulationParametersValues> getParametersValues(@RequestParam(name = "networkUuid") UUID networkUuid,
@RequestParam(name = "variantId", required = false) String variantId,
@RequestBody DynamicSimulationParametersInfos parameters) {
DynamicSimulationParametersValues parametersValues = parametersService.getParametersValues(parameters, networkUuid, variantId);
return ResponseEntity.of(Optional.ofNullable(parametersValues));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
/*
* Copyright (c) 2026, 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.ds.server.dto;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.powsybl.dynawo.DynawoSimulationParameters;
import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig;
import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigsJsonDeserializer;
import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigsJsonSerializer;
import lombok.*;

import java.util.List;

/**
* @author Thang PHAM <quyet-thang.pham at rte-france.com>
*/
@NoArgsConstructor
@AllArgsConstructor
@Getter
@Setter
@Builder
@JsonIgnoreProperties(ignoreUnknown = true)
public class DynamicSimulationParametersValues {
@JsonSerialize(using = DynamicModelConfigsJsonSerializer.class)
@JsonDeserialize(using = DynamicModelConfigsJsonDeserializer.class)
@JsonInclude(JsonInclude.Include.NON_EMPTY)
List<DynamicModelConfig> dynamicModel;

@JsonInclude(JsonInclude.Include.NON_EMPTY)
DynawoSimulationParameters dynawoParameters;
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,13 @@

import com.fasterxml.jackson.databind.ObjectMapper;
import com.powsybl.dynamicsimulation.DynamicSimulationProvider;
import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig;
import com.powsybl.iidm.network.Network;
import com.powsybl.iidm.network.VariantManagerConstants;
import com.powsybl.network.store.client.NetworkStoreService;
import com.powsybl.network.store.client.PreloadingStrategy;
import org.apache.commons.lang3.StringUtils;
import org.gridsuite.computation.s3.ComputationS3Service;
import org.gridsuite.computation.service.AbstractComputationService;
import org.gridsuite.computation.service.NotificationService;
import org.gridsuite.computation.service.UuidGeneratorService;
import org.gridsuite.ds.server.dto.DynamicSimulationStatus;
import org.gridsuite.ds.server.dto.dynamicmapping.InputMapping;
import org.gridsuite.ds.server.service.client.dynamicmapping.DynamicMappingClient;
import org.gridsuite.ds.server.service.contexts.DynamicSimulationResultContext;
import org.gridsuite.ds.server.service.contexts.DynamicSimulationRunContext;
import org.gridsuite.ds.server.service.parameters.ParametersService;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.messaging.Message;
import org.springframework.stereotype.Service;
Expand All @@ -38,24 +29,14 @@
public class DynamicSimulationService extends AbstractComputationService<DynamicSimulationRunContext, DynamicSimulationResultService, DynamicSimulationStatus> {
public static final String COMPUTATION_TYPE = "dynamic simulation";

private final ParametersService parametersService;
private final DynamicMappingClient dynamicMappingClient;
private final NetworkStoreService networkStoreService;

public DynamicSimulationService(
NotificationService notificationService,
ObjectMapper objectMapper,
UuidGeneratorService uuidGeneratorService,
DynamicSimulationResultService dynamicSimulationResultService,
ComputationS3Service computationS3Service,
@Value("${dynamic-simulation.default-provider}") String defaultProvider,
ParametersService parametersService,
DynamicMappingClient dynamicMappingClient,
NetworkStoreService networkStoreService) {
@Value("${dynamic-simulation.default-provider}") String defaultProvider) {
super(notificationService, dynamicSimulationResultService, computationS3Service, objectMapper, uuidGeneratorService, defaultProvider);
this.parametersService = parametersService;
this.dynamicMappingClient = dynamicMappingClient;
this.networkStoreService = networkStoreService;
}

@Override
Expand All @@ -76,13 +57,4 @@ public List<String> getProviders() {
.toList();
}

public List<DynamicModelConfig> exportDynamicModel(UUID networkUuid, String variantId, String mappingName) {

InputMapping inputMapping = dynamicMappingClient.getMapping(mappingName);
Network network = networkStoreService.getNetwork(networkUuid, PreloadingStrategy.COLLECTION);
String variant = StringUtils.isBlank(variantId) ? VariantManagerConstants.INITIAL_VARIANT_ID : variantId;
network.getVariantManager().setWorkingVariant(variant);

return parametersService.getDynamicModel(inputMapping, network);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import com.powsybl.dynawo.DynawoSimulationParameters;
import com.powsybl.dynawo.DynawoSimulationProvider;
import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfig;
import com.powsybl.dynawo.suppliers.dynamicmodels.DynamicModelConfigJsonUtils;
import com.powsybl.dynawo.suppliers.dynamicmodels.DynawoModelsSupplier;
import com.powsybl.dynawo.suppliers.events.DynawoEventModelsSupplier;
import com.powsybl.dynawo.suppliers.events.EventModelConfig;
Expand Down Expand Up @@ -331,7 +332,8 @@ private byte[] zipParameters(DynamicSimulationParameters parameters) {
private byte[] zipDynamicModel(List<DynamicModelConfig> dynamicModelContent) {
byte[] zippedJsonDynamicModelContent;
try {
String jsonDynamicModelContent = objectMapper.writeValueAsString(dynamicModelContent);
// cannot use global shared object mapper for List<DynamicModelConfig> because the Deserializer/Serializer write as an object with "models" root
String jsonDynamicModelContent = DynamicModelConfigJsonUtils.createObjectMapper().writeValueAsString(dynamicModelContent);
zippedJsonDynamicModelContent = Utils.zip(jsonDynamicModelContent);
} catch (IOException e) {
throw new UncheckedIOException("Error occurred while zipping the dynamic model", e);
Expand Down
Loading