production.py 3.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293
  1. import numpy as np
  2. import cvxpy as cp
  3. class PowerSupply:
  4. def __init__(self):
  5. pass
  6. def power(self):
  7. pass
  8. class IntermittentArray(PowerSupply):
  9. def __init__(self, load_factors: np.ndarray, capacity_per_region: np.ndarray):
  10. """Intermittent source model.
  11. This model is suitable for sources with a fixed load factor,
  12. typically intermittent renewables.
  13. :param load_factors: 3D array of shape :math:`S\times T \times R` containing the input load factors.
  14. S is the amount of sources (solar, onshore and offshore wind, etc.)
  15. T is the amount of time steps.
  16. R is the amount of regions.
  17. :type load_factors: np.ndarray
  18. :param capacity_per_region: 2D array of shape :math:`S\times R` containing the installed capacity of each type of source within each region, in GW.
  19. :type capacity_per_region: np.ndarray
  20. """
  21. self.load_factors = load_factors
  22. self.capacity_per_region = capacity_per_region
  23. assert (
  24. (self.load_factors >= 0) & (self.load_factors <= 1)
  25. ).all(), "load factors must be comprised between 0 and 1"
  26. def power(self) -> np.ndarray:
  27. """Power production according to the load factors and installed capacity.
  28. :return: 1D array of size T, containing the power production in GW at each time step.
  29. :rtype: np.ndarray
  30. """
  31. return np.einsum("ijk,ik", self.load_factors, self.capacity_per_region)
  32. class DispatchableArray(PowerSupply):
  33. def __init__(self, dispatchable: np.ndarray):
  34. """Dispatchable power supply.
  35. The model assumes that these sources can be triggered at any time to deliver up to a certain amount of power (the maximum capacity) and up to a certain amount of energy per year.
  36. Their total power output never exceeds the amount of power needed to meet the demand (no waste).
  37. The output is determined by minimizing the deficit of power (demand-supply) over the considered time-range.
  38. :param dispatchable: 2D array of shape :math:`S\times 2` containing the power capacity (in GW) and energy capacity (in GWh) of each of the S sources.
  39. :type dispatchable: np.ndarray
  40. """
  41. self.dispatchable = np.array(dispatchable)
  42. self.n_dispatchable_sources = self.dispatchable.shape[0]
  43. def power(self, gap: np.ndarray) -> np.ndarray:
  44. """Power production optimized for compensating the input gap.
  45. :param gap: 1D array containing the power gap to compensate in GW at each timestep.
  46. :type gap: np.ndarray
  47. :return: 1D array of size T, containing the power production in GW at each time step.
  48. :rtype: np.ndarray
  49. """
  50. # optimize dispatch
  51. T = len(gap)
  52. dispatch_power = cp.Variable((self.n_dispatchable_sources, T))
  53. constraints = (
  54. [dispatch_power >= 0, cp.sum(dispatch_power, axis=0) <= np.maximum(gap, 0)]
  55. + [
  56. dispatch_power[i, :] <= self.dispatchable[i, 0]
  57. for i in range(self.n_dispatchable_sources)
  58. ]
  59. + [
  60. cp.sum(dispatch_power[i]) <= self.dispatchable[i, 1] * T / (365.25 * 24)
  61. for i in range(self.n_dispatchable_sources)
  62. ]
  63. )
  64. prob = cp.Problem(
  65. cp.Minimize(cp.sum(cp.pos(gap - cp.sum(dispatch_power, axis=0)))),
  66. constraints,
  67. )
  68. prob.solve(solver=cp.ECOS, max_iters=300)
  69. dp = dispatch_power.value
  70. return dp