Skip to content

Commit

Permalink
Add solve endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
davidkleiven committed Sep 26, 2023
1 parent 77248b1 commit 7b9bc60
Show file tree
Hide file tree
Showing 5 changed files with 99 additions and 5 deletions.
18 changes: 18 additions & 0 deletions src/main/ApiDataModels.kt
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,21 @@ fun busPropertiesFromNetwork(network: Network): List<BusProperties> {
}
.toList()
}

@Serializable
data class BranchProperties(
val id: String,
val isOverloaded: Boolean
)

@Serializable
data class LoadFlowResultForApi(val isOk: Boolean, val buses: List<BusProperties>, val branches: List<BranchProperties>)

fun branchPropertiesFromNetwork(network: Network): List<BranchProperties> {
return network.lines.map { line ->
BranchProperties(
id = line.id,
isOverloaded = line.isOverloaded
)
}.toList()
}
27 changes: 26 additions & 1 deletion src/main/ApiUtil.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.github.statnett.loadflowservice

import com.powsybl.loadflow.LoadFlowParameters
import com.powsybl.loadflow.LoadFlowResult
import com.powsybl.loadflow.json.JsonLoadFlowParameters
import io.ktor.http.content.*
import java.io.ByteArrayInputStream

Expand All @@ -13,10 +16,32 @@ fun busesFromRequest(

class FileContent(val name: String, val bytes: ByteArray)

suspend fun multiPartDataHandler(multiPartData: MultiPartData): List<FileContent> {
/**
* Convenience class used to deserialize and update a load parameter instance
*/
class LoadParameterContainer() {
var parameters = LoadFlowParameters()
private var parametersModified = false
private fun update(jsonString: String) {
this.parameters = JsonLoadFlowParameters.update(this.parameters, ByteArrayInputStream(jsonString.toByteArray()))
this.parametersModified = true
}

fun formItemHandler(part: PartData.FormItem) {
val name = part.name ?: ""
if (name == "load-parameters") {
this.update(part.value)
}
}
}

suspend fun multiPartDataHandler(multiPartData: MultiPartData, formItemHandler: (part: PartData.FormItem) -> Unit = {}): List<FileContent> {
val files = mutableListOf<FileContent>()
multiPartData.forEachPart { part ->
when (part) {
is PartData.FormItem -> {
formItemHandler(part)
}
is PartData.FileItem -> {
val name = part.originalFileName as String
val content = part.streamProvider().readBytes()
Expand Down
14 changes: 14 additions & 0 deletions src/main/Application.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import io.ktor.server.plugins.contentnegotiation.*
import io.ktor.server.request.*
import io.ktor.server.response.*
import io.ktor.server.routing.*
import java.io.ByteArrayInputStream

fun main(args: Array<String>): Unit = io.ktor.server.netty.EngineMain.main(args)

Expand Down Expand Up @@ -34,5 +35,18 @@ fun Application.module() {
get("/default-load-parameters") {
call.respondText(defaultLoadFlowParameters(), ContentType.Application.Json, HttpStatusCode.OK)
}

post("/run-load-flow") {
val paramContainer = LoadParameterContainer()
val files = multiPartDataHandler(call.receiveMultipart(), paramContainer::formItemHandler)

if (files.isEmpty()) {
call.response.status(HttpStatusCode.UnprocessableEntity)
} else {
val network = networkFromFileContent(files[0])
val result = solve(network, paramContainer.parameters)
call.respond(result)
}
}
}
}
17 changes: 13 additions & 4 deletions src/main/Solver.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,9 @@ package com.github.statnett.loadflowservice
import com.powsybl.iidm.network.Network
import com.powsybl.loadflow.LoadFlow
import com.powsybl.loadflow.LoadFlowParameters
import com.powsybl.loadflow.LoadFlowResult
import com.powsybl.loadflow.json.JsonLoadFlowParameters
import java.io.ByteArrayInputStream
import java.io.ByteArrayOutputStream
import java.io.InputStream

Expand All @@ -14,6 +16,10 @@ fun networkFromStream(
return Network.read(fname, content)
}

fun networkFromFileContent(content: FileContent): Network {
return networkFromStream(content.name, ByteArrayInputStream(content.bytes))
}

fun defaultLoadFlowParameters(): String {
val parameters = LoadFlowParameters()
val stream = ByteArrayOutputStream()
Expand All @@ -26,8 +32,11 @@ fun defaultLoadFlowParameters(): String {
fun solve(
network: Network,
parameters: LoadFlowParameters,
) {
LoadFlow.run(network, parameters)
): LoadFlowResultForApi {
val result = LoadFlow.run(network, parameters)
return LoadFlowResultForApi(
isOk = result.isOk,
buses = busPropertiesFromNetwork(network),
branches = branchPropertiesFromNetwork(network)
)
}


28 changes: 28 additions & 0 deletions src/test/ApplicationTest.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import com.github.statnett.loadflowservice.busPropertiesFromNetwork
import com.powsybl.ieeecdf.converter.IeeeCdfNetworkFactory
import io.ktor.client.request.*
import io.ktor.client.request.forms.formData
Expand All @@ -11,6 +12,7 @@ import io.ktor.server.testing.testApplication
import java.io.File
import java.nio.file.Paths
import java.util.Properties
import kotlin.math.abs
import kotlin.test.Test
import kotlin.test.assertContains
import kotlin.test.assertEquals
Expand Down Expand Up @@ -69,6 +71,32 @@ class ApplicationTest {
assertTrue(body.startsWith("{"))
assertTrue(body.endsWith("}"))
}

@Test
fun `test flow 14 bus network ok`() =
testApplication {
val response = client.submitFormWithBinaryData(
url = "/run-load-flow",
formData = formDataFromFile((ieeeCdfNetwork14File()))
)

assertEquals(response.status, HttpStatusCode.OK)

val body: String = response.bodyAsText()
val solvedNetwork = IeeeCdfNetworkFactory.create14Solved()
val angles = busPropertiesFromNetwork(solvedNetwork).map { bus -> bus.angle }.toList()

val regex = Regex("\"angle\":([0-9.-]+)")
val anglesFromJsonStr = regex.findAll(body).map {match -> match.groupValues[1].toDouble()}.toList()

// It seems like the solved version from Powsybl contains rounded angles
assertTrue(
angles.zip(anglesFromJsonStr).all {
pair -> abs(pair.component1() - pair.component2()) < 0.01
}
)
}

}

fun formDataFromFile(file: File): List<PartData> {
Expand Down

0 comments on commit 7b9bc60

Please sign in to comment.