production_planner.go 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. package spoptim
  2. import (
  3. "math"
  4. "sort"
  5. "encoding/json"
  6. "gem-spaas-coding-challenge/models"
  7. )
  8. var powerplantFuel = map[string]string{"gasfired": "gas(euro/MWh)", "turbojet": "kerosine(euro/MWh)", "windturbine": "wind(%)"}
  9. func sliceSum(slice []float64) float64 {
  10. tot := 0.0
  11. for _, el := range slice {
  12. tot += el
  13. }
  14. return tot
  15. }
  16. func ProductionPlanner(payload *models.Payload) []interface{} {
  17. // handle empty payload
  18. if payload == nil {
  19. return make([]interface{}, 0)
  20. }
  21. // cast the fuels to map and validate
  22. fuels, _ := payload.Fuels.(map[string]interface{})
  23. for k := range powerplantFuel {
  24. if _, ok := fuels[powerplantFuel[k]]; !ok {
  25. res := make([]interface{}, 0)
  26. res = append(res, powerplantFuel[k] + " missing in Fuels")
  27. return res
  28. }
  29. }
  30. // format wind plants for consistency with other types of plants
  31. wind, _ := fuels["wind(%)"].(json.Number).Float64()
  32. for _, plant := range payload.Powerplants {
  33. if *plant.Type == "windturbine" {
  34. *plant.Pmax = math.Round(*plant.Pmax * wind / 10) / 10
  35. }
  36. }
  37. fuels["wind(%)"] = json.Number(0)
  38. // order plants by merit
  39. sort.SliceStable(payload.Powerplants, func(i, j int) bool {
  40. fueli, _ := fuels[powerplantFuel[*payload.Powerplants[i].Type]].(json.Number).Float64()
  41. fuelj, _ := fuels[powerplantFuel[*payload.Powerplants[j].Type]].(json.Number).Float64()
  42. return fueli / *payload.Powerplants[i].Efficiency < fuelj / *payload.Powerplants[j].Efficiency
  43. })
  44. // Naively assign production by merit-order (with one check
  45. // to replace the last plant if too big pmin)
  46. load := 0.0
  47. cost := make([]float64, 0)
  48. res := make([]interface{}, 0)
  49. costToCompare := make([]interface{}, 0)
  50. resToCompare := make([][]interface{}, 0)
  51. for _, plant := range payload.Powerplants {
  52. remainingLoad := *payload.Load - load
  53. if remainingLoad == 0 {
  54. break
  55. }
  56. fuel, _ := fuels[powerplantFuel[*plant.Type]].(json.Number).Float64()
  57. if *plant.Pmin < remainingLoad {
  58. if *plant.Pmax != 0 {
  59. usedP := math.Min(remainingLoad, *plant.Pmax)
  60. load += usedP
  61. cost = append(cost, usedP / *plant.Efficiency * fuel)
  62. res = append(res, map[string]interface{}{
  63. "name": plant.Name,
  64. "p": usedP,
  65. })
  66. }
  67. } else {
  68. altRes := make([]interface{}, len(res))
  69. for i, re := range res {
  70. altRes[i] = map[string]interface{}{
  71. "name": re.(map[string]interface{})["name"],
  72. "p": re.(map[string]interface{})["p"],
  73. }
  74. }
  75. altCost := make([]float64, len(cost))
  76. copy(altCost, cost)
  77. loadToRemove := *plant.Pmin - remainingLoad
  78. for loadToRemove > 0 {
  79. if altRes[len(altRes)-1].(map[string]interface{})["p"].(float64) > loadToRemove {
  80. altCost[len(altCost)-1] = altCost[len(altCost)-1] * (1 - loadToRemove / altRes[len(altRes)-1].(map[string]interface{})["p"].(float64))
  81. altRes[len(altRes)-1].(map[string]interface{})["p"] = altRes[len(altRes)-1].(map[string]interface{})["p"].(float64) - loadToRemove
  82. loadToRemove = 0
  83. } else {
  84. loadToRemove = loadToRemove - altRes[len(altRes)-1].(map[string]interface{})["p"].(float64)
  85. altRes = altRes[:len(altRes)-1]
  86. altCost = altCost[:len(altRes)-1]
  87. }
  88. }
  89. altRes = append(altRes, map[string]interface{}{
  90. "name": *plant.Name,
  91. "p": *plant.Pmin,
  92. })
  93. altCost = append(altCost, *plant.Pmin / *plant.Efficiency * fuel)
  94. resToCompare = append(resToCompare, altRes)
  95. costToCompare = append(costToCompare, altCost)
  96. }
  97. }
  98. bestCost := sliceSum(cost)
  99. for i, altCost := range costToCompare {
  100. if sliceSum(altCost.([]float64)) < bestCost {
  101. bestCost = sliceSum(altCost.([]float64))
  102. res = resToCompare[i]
  103. }
  104. }
  105. return res
  106. }