|
@@ -1,12 +1,114 @@
|
|
|
package spoptim
|
|
package spoptim
|
|
|
|
|
|
|
|
import (
|
|
import (
|
|
|
|
|
+ "fmt"
|
|
|
|
|
+ "math"
|
|
|
|
|
+ "sort"
|
|
|
|
|
+ "strconv"
|
|
|
|
|
+ "encoding/json"
|
|
|
|
|
+
|
|
|
"gem-spaas-coding-challenge/models"
|
|
"gem-spaas-coding-challenge/models"
|
|
|
)
|
|
)
|
|
|
|
|
|
|
|
|
|
+var powerplantFuel = map[string]string{"gasfired": "gas(euro/MWh)", "turbojet": "kerosine(euro/MWh)", "windturbine": "wind(%)"}
|
|
|
|
|
+
|
|
|
|
|
+func sliceSum(slice []float64) float64 {
|
|
|
|
|
+ tot := 0.0
|
|
|
|
|
+ for _, el := range slice {
|
|
|
|
|
+ tot += el
|
|
|
|
|
+ }
|
|
|
|
|
+ return tot
|
|
|
|
|
+}
|
|
|
|
|
+
|
|
|
func ProductionPlanner(payload *models.Payload) []interface{} {
|
|
func ProductionPlanner(payload *models.Payload) []interface{} {
|
|
|
- return []interface{}{map[string]interface{}{"name": "windpark1", "p": 75},
|
|
|
|
|
- map[string]interface{}{"name": "windpark2", "p": 25},
|
|
|
|
|
- map[string]interface{}{"name": "gasfiredbig1", "p": 200}}
|
|
|
|
|
|
|
+ // handle empty payload
|
|
|
|
|
+ if payload == nil {
|
|
|
|
|
+ return make([]interface{}, 0)
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // format wind plants for consistency with other types of plants
|
|
|
|
|
+ wind, _ := payload.Fuels.(map[string]interface{})["wind(%)"].(json.Number).Float64()
|
|
|
|
|
+ for _, plant := range payload.Powerplants {
|
|
|
|
|
+ plant_typed := plant.(map[string]interface{})
|
|
|
|
|
+ if plant_typed["type"] == "windturbine" {
|
|
|
|
|
+ pmax, _ := plant_typed["pmax"].(json.Number).Float64()
|
|
|
|
|
+ plant_typed["pmax"] = json.Number(strconv.FormatFloat((pmax * wind / 100), 'f', 20, 64))
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ payload.Fuels.(map[string]interface{})["wind(%)"] = json.Number(0)
|
|
|
|
|
+
|
|
|
|
|
+ // order plants by merit
|
|
|
|
|
+ sort.SliceStable(payload.Powerplants, func(i, j int) bool {
|
|
|
|
|
+ planti, _ := payload.Powerplants[i].(map[string]interface{})
|
|
|
|
|
+ plantj, _ := payload.Powerplants[j].(map[string]interface{})
|
|
|
|
|
+ fuels, _ := payload.Fuels.(map[string]interface{})
|
|
|
|
|
+ fueli, _ := fuels[powerplantFuel[planti["type"].(string)]].(json.Number).Float64()
|
|
|
|
|
+ fuelj, _ := fuels[powerplantFuel[plantj["type"].(string)]].(json.Number).Float64()
|
|
|
|
|
+ effi, _ := planti["efficiency"].(json.Number).Float64()
|
|
|
|
|
+ effj, _ := plantj["efficiency"].(json.Number).Float64()
|
|
|
|
|
+ return fueli / effi < fuelj / effj
|
|
|
|
|
+ })
|
|
|
|
|
+
|
|
|
|
|
+ // Naively assign production by merit-order (with one check
|
|
|
|
|
+ // to replace the last plant if too big pmin)
|
|
|
|
|
+ load := 0.0
|
|
|
|
|
+ cost := make([]float64, 0)
|
|
|
|
|
+ res := make([]interface{}, 0)
|
|
|
|
|
+ costToCompare := make([]interface{}, 0)
|
|
|
|
|
+ resToCompare := make([][]interface{}, 0)
|
|
|
|
|
+ for _, plant := range payload.Powerplants {
|
|
|
|
|
+ plant_typed := plant.(map[string]interface{})
|
|
|
|
|
+ remainingLoad := *payload.Load - load
|
|
|
|
|
+ if remainingLoad == 0 {
|
|
|
|
|
+ break
|
|
|
|
|
+ }
|
|
|
|
|
+ pmin, _ := plant_typed["pmin"].(json.Number).Float64()
|
|
|
|
|
+ pmax, _ := plant_typed["pmax"].(json.Number).Float64()
|
|
|
|
|
+ eff, _ := plant_typed["efficiency"].(json.Number).Float64()
|
|
|
|
|
+ fuel, _ := payload.Fuels.(map[string]interface{})[powerplantFuel[plant_typed["type"].(string)]].(json.Number).Float64()
|
|
|
|
|
+ if pmin < remainingLoad {
|
|
|
|
|
+ usedP := math.Min(remainingLoad, pmax)
|
|
|
|
|
+ load += usedP
|
|
|
|
|
+ cost = append(cost, usedP / eff * fuel)
|
|
|
|
|
+ res = append(res, map[string]interface{}{
|
|
|
|
|
+ "name": plant_typed["name"].(string),
|
|
|
|
|
+ "p": usedP,
|
|
|
|
|
+ })
|
|
|
|
|
+ } else {
|
|
|
|
|
+ altRes := make([]interface{}, 0)
|
|
|
|
|
+ altCost := make([]float64, 0)
|
|
|
|
|
+ copy(altRes, res)
|
|
|
|
|
+ copy(altCost, cost)
|
|
|
|
|
+ loadToRemove := pmin - remainingLoad
|
|
|
|
|
+ for loadToRemove > 0 {
|
|
|
|
|
+ if altRes[len(altRes)-1].(map[string]interface{})["p"].(float64) > loadToRemove {
|
|
|
|
|
+ altCost[len(altCost)-1] = altCost[len(altCost)-1] * (1 - loadToRemove / altRes[len(altRes)-1].(map[string]interface{})["p"].(float64))
|
|
|
|
|
+ altRes[len(altRes)-1].(map[string]interface{})["p"] = altRes[len(altRes)-1].(map[string]interface{})["p"].(float64) - loadToRemove
|
|
|
|
|
+ loadToRemove = 0
|
|
|
|
|
+ } else {
|
|
|
|
|
+ loadToRemove = loadToRemove - altRes[len(altRes)-1].(map[string]interface{})["p"].(float64)
|
|
|
|
|
+ altRes = altRes[:len(altRes)-1]
|
|
|
|
|
+ altCost = altCost[:len(altRes)-1]
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ altRes = append(altRes, map[string]interface{}{
|
|
|
|
|
+ "name": plant_typed["name"].(string),
|
|
|
|
|
+ "p": pmax,
|
|
|
|
|
+ })
|
|
|
|
|
+ altCost = append(altCost, pmax / eff * fuel)
|
|
|
|
|
+ resToCompare = append(resToCompare, altRes)
|
|
|
|
|
+ costToCompare = append(costToCompare, altCost)
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ bestCost := sliceSum(cost)
|
|
|
|
|
+ for i, altCost := range costToCompare {
|
|
|
|
|
+ if sliceSum(altCost.([]float64)) < bestCost {
|
|
|
|
|
+ bestCost = sliceSum(altCost.([]float64))
|
|
|
|
|
+ res = resToCompare[i]
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ return res
|
|
|
}
|
|
}
|
|
|
|
|
|