production_planner.go 3.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111
  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 = *plant.Pmax * wind / 100
  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. usedP := math.Min(remainingLoad, *plant.Pmax)
  59. load += usedP
  60. cost = append(cost, usedP / *plant.Efficiency * fuel)
  61. res = append(res, map[string]interface{}{
  62. "name": plant.Name,
  63. "p": usedP,
  64. })
  65. } else {
  66. altRes := make([]interface{}, 0)
  67. altCost := make([]float64, 0)
  68. copy(altRes, res)
  69. copy(altCost, cost)
  70. loadToRemove := *plant.Pmin - remainingLoad
  71. for loadToRemove > 0 {
  72. if altRes[len(altRes)-1].(map[string]interface{})["p"].(float64) > loadToRemove {
  73. altCost[len(altCost)-1] = altCost[len(altCost)-1] * (1 - loadToRemove / altRes[len(altRes)-1].(map[string]interface{})["p"].(float64))
  74. altRes[len(altRes)-1].(map[string]interface{})["p"] = altRes[len(altRes)-1].(map[string]interface{})["p"].(float64) - loadToRemove
  75. loadToRemove = 0
  76. } else {
  77. loadToRemove = loadToRemove - altRes[len(altRes)-1].(map[string]interface{})["p"].(float64)
  78. altRes = altRes[:len(altRes)-1]
  79. altCost = altCost[:len(altRes)-1]
  80. }
  81. }
  82. altRes = append(altRes, map[string]interface{}{
  83. "name": *plant.Name,
  84. "p": *plant.Pmax,
  85. })
  86. altCost = append(altCost, *plant.Pmax / *plant.Efficiency * fuel)
  87. resToCompare = append(resToCompare, altRes)
  88. costToCompare = append(costToCompare, altCost)
  89. }
  90. }
  91. bestCost := sliceSum(cost)
  92. for i, altCost := range costToCompare {
  93. if sliceSum(altCost.([]float64)) < bestCost {
  94. bestCost = sliceSum(altCost.([]float64))
  95. res = resToCompare[i]
  96. }
  97. }
  98. return res
  99. }