├── .gitignore ├── .travis.yml ├── Dockerfile ├── LICENSE ├── README.md ├── cvxpower ├── __init__.py ├── devices.py ├── devices_test.py ├── network.py └── network_test.py ├── docs ├── Makefile ├── _static │ └── dynamic_load_results.jpg ├── conf.py ├── devices.rst ├── examples.rst ├── get_started.rst ├── index.rst ├── network.rst └── notebooks │ ├── dynamic.rst │ ├── dynamic_files │ ├── dynamic_10_1.png │ ├── dynamic_10_2.png │ ├── dynamic_11_1.png │ ├── dynamic_11_2.png │ ├── dynamic_4_1.png │ ├── dynamic_5_1.png │ ├── dynamic_6_1.png │ ├── dynamic_6_2.png │ ├── dynamic_7_1.png │ ├── dynamic_7_2.png │ ├── dynamic_8_1.png │ └── dynamic_9_1.png │ ├── microgrid.rst │ ├── smarthome.rst │ ├── static.rst │ ├── static_files │ └── static_14_1.png │ ├── three_bus.png │ ├── windfarm.rst │ └── windfarm_files │ ├── windfarm_10_1.png │ ├── windfarm_10_2.png │ ├── windfarm_11_1.png │ ├── windfarm_11_2.png │ ├── windfarm_12_1.png │ ├── windfarm_13_1.png │ ├── windfarm_15_1.png │ ├── windfarm_17_1.png │ ├── windfarm_17_2.png │ ├── windfarm_18_1.png │ ├── windfarm_3_1.png │ ├── windfarm_5_1.png │ └── windfarm_8_1.png ├── examples ├── CaliforniaSolarEnergy.ipynb ├── HistoricalEMSHourlyLoad_2014-2016.xlsx ├── HomeEnergy.ipynb ├── ThreeBusExample.ipynb ├── WindForecast.ipynb ├── dynamic.ipynb ├── gen_bid_curve.py ├── ieee14cdf.txt ├── nrel_wind │ ├── 20182-2007.csv │ ├── 20182-2008.csv │ ├── 20182-2009.csv │ ├── 20182-2010.csv │ ├── 20182-2011.csv │ └── 20182-2012.csv ├── p_wind.pickle ├── residual_params.pickle ├── sigma_epsilon.pickle ├── static.ipynb ├── three_bus.png ├── wind_baseline.pickle ├── wind_power_test.pickle ├── wind_power_train.pickle └── windfarm.ipynb ├── setup.py └── tools ├── generate_notebooks.sh ├── push_docker.sh └── run_ci.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # notebook 2 | .ipynb_checkpoints 3 | 4 | # python 5 | *.pyc 6 | *.egg-info 7 | .eggs 8 | 9 | # sphinx 10 | _build 11 | 12 | #OSX 13 | *.DS_Store 14 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | sudo: required 2 | 3 | language: python 4 | 5 | services: 6 | - docker 7 | 8 | before_install: 9 | - docker pull cvxgrp/dem 10 | 11 | script: 12 | - docker run -v $TRAVIS_BUILD_DIR:/build/dem cvxgrp/dem /build/dem/tools/run_ci.sh 13 | 14 | notifications: 15 | email: false 16 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM debian:latest 2 | 3 | # Debian packages 4 | RUN apt-get update && apt-get install -y \ 5 | autoconf \ 6 | autotools-dev \ 7 | build-essential \ 8 | bzip2 \ 9 | cmake \ 10 | curl \ 11 | g++ \ 12 | gfortran \ 13 | git \ 14 | libc-dev \ 15 | libopenblas-dev \ 16 | libquadmath0 \ 17 | libtool \ 18 | make \ 19 | parallel \ 20 | pkg-config \ 21 | unzip \ 22 | timelimit \ 23 | wget \ 24 | zip && apt-get clean 25 | 26 | # Python 2 27 | RUN apt-get install -y \ 28 | python-dev \ 29 | python-pip 30 | RUN python2 -m pip install -U pip 31 | RUN python2 -m pip install -U numpy scipy 32 | RUN python2 -m pip install -U cvxpy 33 | 34 | # Python 3 35 | RUN apt-get install -y \ 36 | python3-dev \ 37 | python3-pip 38 | RUN python3 -m pip install -U pip 39 | RUN python3 -m pip install -U numpy scipy 40 | RUN python3 -m pip install -U cvxpy 41 | 42 | CMD ["bash"] 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "{}" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright 2017 Steven Diamond 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # `cvxpower` 2 | 3 | A library for static, dynamic, uncertain, and robust optimization of power networks, implementing the models from our [paper](https://web.stanford.edu/~boyd/papers/dyn_ener_man.html) 4 | 5 | ``` 6 | @incollection{MBBW:18, 7 | author={N. Moehle and E. Busseti and S. Boyd and M. Wytock}, 8 | title={Dynamic Energy Management}, 9 | booktitle={Large Scale Optimization in Supply Chains and Smart Manufacturing}, 10 | pages={69--126}, 11 | year={2019}, 12 | publisher={Springer} 13 | } 14 | ``` 15 | 16 | 17 | See the [examples](https://github.com/cvxgrp/cvxpower/tree/master/examples) for basic usage, or the (outdated) [documentation](http://energy-management.readthedocs.io/). 18 | -------------------------------------------------------------------------------- /cvxpower/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Matt Wytock, Enzo Busseti, Nicholas Moehle, 2016-2019. 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Code written by Nicholas Moehle before January 2019 is licensed 16 | under the Apache License, Version 2.0 (the "License"); 17 | you may not use this file except in compliance with the License. 18 | You may obtain a copy of the License at 19 | 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | 22 | Unless required by applicable law or agreed to in writing, software 23 | distributed under the License is distributed on an "AS IS" BASIS, 24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | See the License for the specific language governing permissions and 26 | limitations under the License. 27 | """ 28 | 29 | from .network import * 30 | from .devices import * 31 | from cvxpy import Parameter 32 | -------------------------------------------------------------------------------- /cvxpower/devices.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Matt Wytock, Enzo Busseti, Nicholas Moehle, 2016-2019. 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Code written by Nicholas Moehle before January 2019 is licensed 16 | under the Apache License, Version 2.0 (the "License"); 17 | you may not use this file except in compliance with the License. 18 | You may obtain a copy of the License at 19 | 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | 22 | Unless required by applicable law or agreed to in writing, software 23 | distributed under the License is distributed on an "AS IS" BASIS, 24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | See the License for the specific language governing permissions and 26 | limitations under the License. 27 | """ 28 | 29 | 30 | __doc__ = r"""Device models for dynamic energy management. 31 | 32 | Each device has one or more terminals each of which has associated with it a 33 | vector representing power consumption or generation. Formally, we denote this 34 | power schedule as :math:`p = (p(1), \ldots, p(T)) \in \mathbb{R}^T` and devices 35 | specify the objective function and constraints imposed by each device over these 36 | power flows. 37 | """ 38 | 39 | import cvxpy as cvx 40 | import numpy as np 41 | 42 | from .network import Device, Terminal 43 | 44 | 45 | class Generator(Device): 46 | r"""Generator with quadratic cost and optional ramp constraints. 47 | 48 | A generator has range and ramp rate constraints defining the operating 49 | region, for :math:`\tau = 1,\ldots,T` 50 | 51 | .. math:: 52 | 53 | P^{\min} \le -p(\tau) \le P^{\max} \\ 54 | R^{\min} \le -(p(\tau) - p(\tau - 1)) \le R^{\max} 55 | 56 | .. 57 | 58 | where :math:`p(0)` is defined to be :math:`p^\mathrm{init}` is the initial 59 | power produced by the generator. If :math:`R^{\min}` or :math:`R^{\max}` are 60 | unspecified, the minimum or maximum ramp rate is unconstrained, respectively. 61 | 62 | The cost function is separable across time periods 63 | 64 | .. math:: 65 | 66 | \sum_{\tau=1}^T \phi(-p(\tau)) 67 | 68 | .. 69 | 70 | and quadratic, :math:`\phi(x) = \alpha x^2 + \beta x`, parameterized by 71 | :math:`\alpha, \beta \in \mathbb{R}`. 72 | 73 | :param power_min: Minimum power generation, :math:`P^\min` 74 | :param power_max: Maximum power generation, :math:`P^\max` 75 | :param alpha: Quadratic term in cost function, :math:`\alpha` 76 | :param beta: Linear term in cost function, :math:`\beta` 77 | :param ramp_min: (optional) Minimum ramp rate, :math:`R^\min` 78 | :param ramp_max: (optional) Maximum ramp rate, :math:`R^\max` 79 | :param power_init: (optional) Initial power generation for ramp constraints, :math:`p^\mathrm{init}` 80 | :param name: (optional) Display name for generator 81 | :type power_min: float or sequence of float 82 | :type power_max: float or sequence of float 83 | :type alpha: float or sequence of float 84 | :type beta: float or sequence of float 85 | :type ramp_min: float or sequence of float 86 | :type ramp_max: float or sequence of float 87 | :type power_init: float 88 | :type name: string 89 | """ 90 | 91 | def __init__( 92 | self, 93 | power_min=None, 94 | power_max=None, 95 | ramp_min=None, 96 | ramp_max=None, 97 | power_init=0, 98 | alpha=0, 99 | beta=0, 100 | gamma=0, 101 | name=None, 102 | len_interval=1, 103 | ): 104 | super(Generator, self).__init__([Terminal()], name) 105 | self.power_min = power_min 106 | self.power_max = power_max 107 | self.ramp_min = ramp_min 108 | self.ramp_max = ramp_max 109 | self.power_init = power_init 110 | self.alpha = alpha 111 | self.beta = beta 112 | self.gamma = gamma 113 | self.len_interval = len_interval # in hours 114 | 115 | @property 116 | def cost(self): 117 | p = self.terminals[0].power_var 118 | return self.alpha * cvx.square(p) - self.beta * p + self.gamma 119 | 120 | @property 121 | def constraints(self): 122 | p = -self.terminals[0].power_var 123 | 124 | constraints = [] 125 | if self.power_min is not None: 126 | constraints += [p >= self.power_min] 127 | if self.power_max is not None: 128 | constraints += [p <= self.power_max] 129 | 130 | # TODO(mwytock): Add ramp constraints 131 | 132 | return constraints 133 | 134 | 135 | class FixedLoad(Device): 136 | """Fixed load. 137 | 138 | A fixed load has a specified profile :math:`l \in \mathbb{R}^T`, 139 | enforced with constraint 140 | 141 | .. math:: 142 | p = l. 143 | .. 144 | 145 | :param power: Load profile, :math:`l` 146 | :param name: (optional) Display name for load 147 | :type power: float or sequence of float 148 | :type name: string 149 | """ 150 | 151 | def __init__(self, power=None, name=None): 152 | super(FixedLoad, self).__init__([Terminal()], name) 153 | self.power = power 154 | 155 | @property 156 | def constraints(self): 157 | if self.terminals[0].power_var.shape[1] == 1: 158 | p = ( 159 | self.power[:, 0] 160 | if (not np.isscalar(self.power) and self.power.ndim == 2) 161 | else self.power 162 | ) 163 | return [self.terminals[0].power_var[:, 0] == p] 164 | else: 165 | return [self.terminals[0].power_var == self.power] 166 | 167 | 168 | class ThermalLoad(Device): 169 | r"""Thermal load. 170 | 171 | A thermal load consists of a heat store (e.g. building, refrigerator), with 172 | a temperature profile :math:`\theta \in \mathbb{R}^T` which must be 173 | maintained within :math:`[\theta^\min,\theta^\max]`. The temperature evolves 174 | as 175 | 176 | .. math:: 177 | \theta(\tau + 1) = \theta(\tau) + (\mu/c)(\theta^\mathrm{amb}(\tau) - 178 | \theta(\tau)) - (\eta/c)p(\tau) 179 | .. 180 | 181 | for :math:`\tau = 1,\ldots,T-1` and :math:`\theta(1) = 182 | \theta^{\mathrm{init}}`, where :math:`\mu` is the ambient conduction 183 | coefficient, :math:`\eta` is the heating/cooling efficiency, :math:`c` is 184 | the heat capacity of the heat store, :math:`\theta \in \mathbb{R}^T` is the 185 | ambient temperature profile and :math:`\theta^{\mathrm{init}}` is the 186 | initial temperature of the heat store. 187 | 188 | The power consumption of is constrained by 189 | 190 | .. math:: 191 | 0 \le p \le P^\max. 192 | .. 193 | 194 | :param temp_init: Initial tempeature, :math:`\theta^\mathrm{init}` 195 | :param temp_min: Minimum temperature, :math:`\theta^\min` 196 | :param temp_max: Maximum temperature, :math:`\theta^\max` 197 | :param temp_amb: Ambient temperature, :math:`\theta^\mathrm{amb}` 198 | :param power_max: Maximum power consumption, :math:`P^\max` 199 | :param amb_conduct_coeff: Ambient conduction coefficient, :math:`\mu` 200 | :param efficiency: Heating/cooling efficiency, :math:`\eta` 201 | :param capacity: Heat capacity of the heat store, :math:`c` 202 | :type temp_init: float 203 | :type temp_min: float or sequence of floats 204 | :type temp_max: float or sequence of floats 205 | :type temp_amb: float or sequence of floats 206 | :type power_max: float or sequence of floats 207 | :type amb_conduct_coeff: float or sequence of floats 208 | :type efficiency: float or sequence of floats 209 | :type capacity: float or sequence of floats 210 | """ 211 | 212 | def __init__( 213 | self, 214 | temp_init=None, 215 | temp_min=None, 216 | temp_max=None, 217 | temp_amb=None, 218 | power_max=None, 219 | amb_conduct_coeff=None, 220 | efficiency=None, 221 | capacity=None, 222 | name=None, 223 | ): 224 | super(ThermalLoad, self).__init__([Terminal()], name) 225 | self.temp_init = temp_init 226 | self.temp_min = temp_min 227 | self.temp_max = temp_max 228 | self.temp_amb = temp_amb 229 | self.power_max = power_max 230 | self.amb_conduct_coeff = amb_conduct_coeff 231 | self.efficiency = efficiency 232 | self.capacity = capacity 233 | 234 | @property 235 | def constraints(self): 236 | alpha = self.amb_conduct_coeff / self.capacity 237 | beta = self.efficiency / self.capacity 238 | N = self.terminals[0].power_var.shape[0] 239 | self.temp = cvx.Variable(shape=(N, 1)) 240 | 241 | constrs = [ 242 | self.terminals[0].power_var <= self.power_max, 243 | self.terminals[0].power_var >= 0, 244 | ] 245 | 246 | if self.temp_max is not None: 247 | constrs += [self.temp <= self.temp_max] 248 | if self.temp_min is not None: 249 | constrs += [self.temp >= self.temp_min] 250 | 251 | for i in range(N): 252 | temp_prev = self.temp[i - 1] if i else self.temp_init 253 | constrs += [ 254 | self.temp[i] 255 | == ( 256 | temp_prev 257 | + alpha * (self.temp_amb[i] - temp_prev) 258 | - beta * self.terminals[0].power_var[i] 259 | ) 260 | ] 261 | 262 | return constrs 263 | 264 | 265 | class CurtailableLoad(Device): 266 | r"""Curtailable load. 267 | 268 | A curtailable load penalizes the shortfall between a desired load profile 269 | :math:`l \in \mathbb{R}^T` and delivered power with the linear penalty 270 | 271 | .. math:: 272 | 273 | \alpha \sum_{\tau=1}^T (l(\tau) - p(\tau))_+ 274 | 275 | .. 276 | 277 | where :math:`(z)_+ = \max(0, z)` and :math:`\alpha > 0` is curtailment 278 | penalty. Conceptually, the linear curtailment penalty represents the price 279 | paid for reducing load. 280 | 281 | :param power: Desired load profile, :math:`l` 282 | :param alpha: Linear curtailment penalty, :math:`\alpha` 283 | :param name: (optional) Display name for load 284 | :type power: float or sequence of float 285 | :type alpha: float or sequence of float 286 | :type name: string 287 | """ 288 | 289 | def __init__(self, power=None, alpha=None, name=None): 290 | super(CurtailableLoad, self).__init__([Terminal()], name) 291 | self.power = power 292 | self.alpha = alpha 293 | 294 | @property 295 | def cost(self): 296 | return self.alpha * cvx.pos(self.power - self.terminals[0].power_var) 297 | 298 | 299 | class DeferrableLoad(Device): 300 | r"""Deferrable load. 301 | 302 | A deferrable load must consume a certain amount of energy :math:`E` over a 303 | flexible time horizon :math:`\tau = A,\ldots,D`. This is characterized by 304 | the constraint 305 | 306 | .. math:: 307 | \sum_{\tau=A}^{D} p(\tau) \ge E 308 | .. 309 | 310 | In addition, the power consumption in any interval is bounded by 311 | 312 | .. math:: 313 | p \le P^\max 314 | .. 315 | 316 | :param energy: Desired energy consumption, :math:`E` 317 | :param power_max: Maximum power consumption, :math:`P^\max` 318 | :param time_start: Starting interval, inclusive, :math:`A` 319 | :param time_end: Ending interval, inclusive, :math:`D` 320 | :param name: (optional) Display name for load 321 | :type energy: float 322 | :type power_max: float or sequence of float 323 | :type time_start: int 324 | :type time_end: int 325 | :type name: string 326 | """ 327 | 328 | def __init__( 329 | self, 330 | energy=0, 331 | power_max=None, 332 | time_start=0, 333 | time_end=None, 334 | name=None, 335 | len_interval=1.0, 336 | ): 337 | super(DeferrableLoad, self).__init__([Terminal()], name) 338 | self.time_start = time_start 339 | self.time_end = time_end 340 | self.energy = energy 341 | self.power_max = power_max 342 | self.len_interval = len_interval 343 | 344 | @property 345 | def constraints(self): 346 | idx = slice(self.time_start, self.time_end) 347 | return [ 348 | cvx.sum(self.terminals[0].power_var[idx]) * self.len_interval 349 | == self.energy, 350 | self.terminals[0].power_var >= 0, 351 | self.terminals[0].power_var <= self.power_max, 352 | ] 353 | 354 | 355 | class TransmissionLine(Device): 356 | """Transmission line. 357 | 358 | A lossless transmission line has two terminals with power schedules 359 | :math:`p_1` and :math:`p_2`. Conservation of energy across the 360 | line is enforced with the constraint 361 | 362 | .. math:: 363 | p_1 + p_2 = 0, 364 | .. 365 | 366 | and a maximum capacity of :math:`P^\max` with 367 | 368 | .. math:: 369 | |p_1| \le P^\max. 370 | .. 371 | 372 | :param power_max: Maximum capacity of the transmission line 373 | :param name: (optional) Display name for transmission line 374 | :type power_max: float or sequence of floats 375 | :type name: string 376 | """ 377 | 378 | def __init__(self, alpha=0.0, power_max=None, name=None): 379 | super(TransmissionLine, self).__init__([Terminal(), Terminal()], name) 380 | self.power_max = power_max 381 | self.alpha = alpha 382 | assert self.alpha >= 0 383 | 384 | @property 385 | def constraints(self): 386 | p1 = self.terminals[0].power_var 387 | p2 = self.terminals[1].power_var 388 | 389 | constrs = [] 390 | if self.alpha > 0: 391 | constrs += [p1 + p2 >= self.alpha * cvx.square((p1 - p2) / 2)] 392 | if self.power_max is not None: 393 | constrs += [2 * self.alpha * self.power_max ** 2 >= p1 + p2] 394 | else: 395 | constrs += [p1 + p2 == 0] 396 | if self.power_max is not None: 397 | constrs += [cvx.abs((p1 - p2) / 2) <= self.power_max] 398 | 399 | return constrs 400 | 401 | 402 | class Storage(Device): 403 | r"""Storage device. 404 | 405 | A storage device either takes or delivers power with charging and 406 | discharging rates specified by the constraints 407 | 408 | .. math:: 409 | -D^\max \le p \le C^\max 410 | .. 411 | 412 | where :math:`C^\max` and :math:`D^\max` are the maximum charging and 413 | discharging rates. The charge level of the battery is given by 414 | 415 | .. math:: 416 | q(\tau) = q^\mathrm{init} + \sum_{t=1}^\tau p(t), \quad \tau = 1, \ldots, T, 417 | .. 418 | 419 | which is constrained according to the physical limits of the battery 420 | 421 | .. math:: 422 | 0 \le q \le Q^\max. 423 | .. 424 | 425 | :param discharge_max: Maximum discharge rate, :math:`D^\max` 426 | :param charge_max: Maximum charge rate, :math:`C^\max` 427 | :param energy_init: Initial charge, :math:`q^\mathrm{init}` 428 | :param energy_max: Maximum battery capacity, :math:`Q^\max` 429 | :param name: (optional) Display name of storage device 430 | :type discharge_max: float or sequence of floats 431 | :type charge_max: float or sequence of floats 432 | :type energy_init: float 433 | :type energy_max: float or sequence of floats 434 | :type name: string 435 | """ 436 | 437 | def __init__( 438 | self, 439 | discharge_max=0, 440 | charge_max=None, 441 | energy_init=0, 442 | energy_final=None, 443 | energy_max=None, 444 | name=None, 445 | len_interval=1.0, 446 | final_energy_price=None, 447 | ): 448 | super(Storage, self).__init__([Terminal()], name) 449 | self.discharge_max = discharge_max 450 | self.charge_max = charge_max 451 | self.energy_init = energy_init 452 | self.energy_max = energy_max 453 | self.energy_final = energy_final 454 | self.len_interval = len_interval # in hours 455 | self.final_energy_price = final_energy_price 456 | self.energy = None 457 | 458 | @property 459 | def cost(self): 460 | T, S = self.terminals[0].power_var.shape 461 | if self.final_energy_price is not None: 462 | if self.energy is None: 463 | self.energy = cvx.Variable(self.terminals[0].power_var.shape) 464 | cost = np.zeros((T - 1, S)) 465 | final_cost = cvx.reshape( 466 | self.energy[-1, :] * self.final_energy_price[0, 0], (1, S) 467 | ) 468 | cost = cvx.vstack([cost, final_cost]) 469 | else: 470 | cost = np.zeros(T, S) 471 | return cost 472 | 473 | @property 474 | def constraints(self): 475 | P = self.terminals[0].power_var 476 | if self.energy is None: 477 | self.energy = cvx.Variable(self.terminals[0].power_var.shape) 478 | e_init = cvx.reshape(self.energy_init, ()) 479 | constr = [ 480 | cvx.diff(self.energy) == P[1:, :] * self.len_interval, 481 | self.energy[0, :] - e_init - P[0, :] * self.len_interval == 0, 482 | self.terminals[0].power_var >= -self.discharge_max, 483 | self.terminals[0].power_var <= self.charge_max, 484 | self.energy <= self.energy_max, 485 | self.energy >= 0, 486 | ] 487 | if self.energy_final is not None: 488 | constr += [self.energy[-1] >= self.energy_final] 489 | return constr 490 | -------------------------------------------------------------------------------- /cvxpower/devices_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Matt Wytock, Enzo Busseti, Nicholas Moehle, 2016-2019. 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Code written by Nicholas Moehle before January 2019 is licensed 16 | under the Apache License, Version 2.0 (the "License"); 17 | you may not use this file except in compliance with the License. 18 | You may obtain a copy of the License at 19 | 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | 22 | Unless required by applicable law or agreed to in writing, software 23 | distributed under the License is distributed on an "AS IS" BASIS, 24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | See the License for the specific language governing permissions and 26 | limitations under the License. 27 | """ 28 | 29 | import cvxpy as cvx 30 | import numpy as np 31 | import unittest 32 | 33 | from cvxpower import * 34 | 35 | 36 | class StaticTest(unittest.TestCase): 37 | def test_hello_world(self): 38 | load = FixedLoad(power=100) 39 | gen = Generator(power_max=1000, alpha=0.1, beta=100) 40 | net = Net([load.terminals[0], gen.terminals[0]]) 41 | network = Group([load, gen], [net]) 42 | network.optimize(solver="ECOS") 43 | np.testing.assert_allclose(load.terminals[0].power, 100) 44 | np.testing.assert_allclose(gen.terminals[0].power, -100) 45 | np.testing.assert_allclose(net.price, 120, rtol=1e-2) 46 | np.testing.assert_allclose(load.terminals[0].payment, 12000, rtol=1e-2) 47 | np.testing.assert_allclose(gen.terminals[0].payment, -12000, rtol=1e-2) 48 | 49 | def test_curtailable_load(self): 50 | load = CurtailableLoad(power=1000, alpha=150) 51 | gen = Generator(power_max=1000, alpha=1, beta=100) 52 | net = Net([load.terminals[0], gen.terminals[0]]) 53 | network = Group([load, gen], [net]) 54 | network.optimize(solver="ECOS") 55 | 56 | np.testing.assert_allclose(load.terminals[0].power, 25.00, rtol=1e-2) 57 | np.testing.assert_allclose(gen.terminals[0].power, -25, rtol=1e-2) 58 | np.testing.assert_allclose(net.price, 150, rtol=1e-2) 59 | 60 | def test_two_generators_with_transmission(self): 61 | load = FixedLoad(power=100) 62 | gen1 = Generator(power_max=1000, alpha=0.01, beta=100, name="Gen1") 63 | gen2 = Generator(power_max=100, alpha=0.1, beta=0.1, name="Gen2") 64 | line = TransmissionLine(power_max=50) 65 | 66 | net1 = Net([load.terminals[0], gen1.terminals[0], line.terminals[0]]) 67 | net2 = Net([gen2.terminals[0], line.terminals[1]]) 68 | 69 | network = Group([load, gen1, gen2, line], [net1, net2]) 70 | network.optimize(solver="ECOS") 71 | 72 | np.testing.assert_allclose(load.terminals[0].power, 100, rtol=1e-2) 73 | np.testing.assert_allclose(gen1.terminals[0].power, -50, rtol=1e-2) 74 | np.testing.assert_allclose(gen2.terminals[0].power, -50, rtol=1e-2) 75 | np.testing.assert_allclose(line.terminals[0].power, -50, rtol=1e-2) 76 | np.testing.assert_allclose(line.terminals[1].power, 50, rtol=1e-2) 77 | 78 | np.testing.assert_allclose(net1.price, 101, rtol=1e-2) 79 | np.testing.assert_allclose(net2.price, 10.1, rtol=1e-2) 80 | 81 | def test_three_buses(self): 82 | load1 = FixedLoad(power=50, name="Load1") 83 | load2 = FixedLoad(power=100, name="Load2") 84 | gen1 = Generator(power_max=1000, alpha=0.01, beta=100, name="Gen1") 85 | gen2 = Generator(power_max=100, alpha=0.1, beta=0.1, name="Gen2") 86 | line1 = TransmissionLine(power_max=50) 87 | line2 = TransmissionLine(power_max=10) 88 | line3 = TransmissionLine(power_max=55) 89 | 90 | net1 = Net( 91 | [ 92 | load1.terminals[0], 93 | gen1.terminals[0], 94 | line1.terminals[0], 95 | line2.terminals[0], 96 | ] 97 | ) 98 | net2 = Net([load2.terminals[0], line1.terminals[1], line3.terminals[0]]) 99 | net3 = Net([gen2.terminals[0], line2.terminals[1], line3.terminals[1]]) 100 | 101 | network = Group( 102 | [load1, load2, gen1, gen2, line1, line2, line3], [net1, net2, net3] 103 | ) 104 | network.optimize(solver="ECOS") 105 | 106 | np.testing.assert_allclose(load1.terminals[0].power, 50, rtol=1e-2) 107 | np.testing.assert_allclose(load2.terminals[0].power, 100, rtol=1e-2) 108 | np.testing.assert_allclose(gen1.terminals[0].power, -85, rtol=1e-2) 109 | np.testing.assert_allclose(gen2.terminals[0].power, -65, rtol=1e-2) 110 | np.testing.assert_allclose(line1.terminals[0].power, 45, rtol=1e-2) 111 | np.testing.assert_allclose(line1.terminals[1].power, -45, rtol=1e-2) 112 | np.testing.assert_allclose(line2.terminals[0].power, -10, rtol=1e-2) 113 | np.testing.assert_allclose(line2.terminals[1].power, 10, rtol=1e-2) 114 | np.testing.assert_allclose(line3.terminals[0].power, -55, rtol=1e-2) 115 | np.testing.assert_allclose(line3.terminals[1].power, 55, rtol=1e-2) 116 | 117 | np.testing.assert_allclose(net1.price, 101.7, rtol=1e-2) 118 | np.testing.assert_allclose(net2.price, 101.7, rtol=1e-2) 119 | np.testing.assert_allclose(net3.price, 13.1, rtol=1e-2) 120 | 121 | def test_group(self): 122 | solar = Generator(power_max=10, alpha=0, beta=0, name="Solar") 123 | load = FixedLoad(power=13) 124 | line = TransmissionLine(power_max=25) 125 | net = Net([load.terminals[0], solar.terminals[0], line.terminals[0]]) 126 | home = Group([solar, load, line], [net], [line.terminals[1]], name="Home") 127 | 128 | grid = Generator(power_max=1e6, alpha=0.05, beta=100, name="Grid") 129 | meter = Net([line.terminals[1], grid.terminals[0]], name="Meter") 130 | 131 | network = Group([home, grid], [meter]) 132 | network.init_problem() 133 | network.problem.solve(solver="ECOS") 134 | 135 | np.testing.assert_allclose(home.terminals[0].power, 3) 136 | np.testing.assert_allclose(grid.terminals[0].power, -3) 137 | 138 | np.testing.assert_allclose(net.price, 100.3, rtol=1e-2) 139 | 140 | def test_vary_parameters(self): 141 | load1 = FixedLoad(power=50, name="Load1") 142 | load2 = FixedLoad(power=100, name="Load2") 143 | gen1 = Generator(power_max=100, alpha=1, beta=10, name="Gen1") 144 | gen2 = Generator(power_max=1000, alpha=0.01, beta=0, name="Gen2") 145 | line1 = TransmissionLine(power_max=100) 146 | line2 = TransmissionLine(power_max=10) 147 | line3 = TransmissionLine(power_max=Parameter(1)) 148 | 149 | net1 = Net( 150 | [ 151 | load1.terminals[0], 152 | gen1.terminals[0], 153 | line1.terminals[0], 154 | line2.terminals[0], 155 | ] 156 | ) 157 | net2 = Net([load2.terminals[0], line1.terminals[1], line3.terminals[0]]) 158 | net3 = Net([gen2.terminals[0], line2.terminals[1], line3.terminals[1]]) 159 | network = Group( 160 | [load1, load2, gen1, gen2, line1, line2, line3], [net1, net2, net3] 161 | ) 162 | network.init_problem() 163 | prob = network.problem 164 | 165 | line3.power_max.value = [50] 166 | prob.solve(solver="ECOS") 167 | np.testing.assert_allclose(load1.terminals[0].power, 50, rtol=1e-2) 168 | np.testing.assert_allclose(load2.terminals[0].power, 100, rtol=1e-2) 169 | np.testing.assert_allclose(gen1.terminals[0].power, -90, rtol=1e-2) 170 | np.testing.assert_allclose(gen2.terminals[0].power, -60, rtol=1e-2) 171 | np.testing.assert_allclose(line1.terminals[0].power, 50, rtol=1e-2) 172 | np.testing.assert_allclose(line1.terminals[1].power, -50, rtol=1e-2) 173 | np.testing.assert_allclose(line2.terminals[0].power, -10, rtol=1e-2) 174 | np.testing.assert_allclose(line2.terminals[1].power, 10, rtol=1e-2) 175 | np.testing.assert_allclose(line3.terminals[0].power, -50, rtol=1e-2) 176 | np.testing.assert_allclose(line3.terminals[1].power, 50, rtol=1e-2) 177 | 178 | np.testing.assert_allclose(net1.price, 190.0136, rtol=1e-2) 179 | np.testing.assert_allclose(net2.price, 190.0136, rtol=1e-2) 180 | np.testing.assert_allclose(net3.price, 1.2000, rtol=1e-2) 181 | 182 | line3.power_max.value = [100] 183 | prob.solve(solver="ECOS") 184 | np.testing.assert_allclose(load1.terminals[0].power, 50, rtol=1e-2) 185 | np.testing.assert_allclose(load2.terminals[0].power, 100, rtol=1e-2) 186 | np.testing.assert_allclose(gen1.terminals[0].power, -40, rtol=1e-2) 187 | np.testing.assert_allclose(gen2.terminals[0].power, -110, rtol=1e-2) 188 | np.testing.assert_allclose(line1.terminals[0].power, 0, atol=1e-4) 189 | np.testing.assert_allclose(line1.terminals[1].power, 0, atol=1e-4) 190 | np.testing.assert_allclose(line2.terminals[0].power, -10, rtol=1e-2) 191 | np.testing.assert_allclose(line2.terminals[1].power, 10, rtol=1e-2) 192 | np.testing.assert_allclose(line3.terminals[0].power, -100, rtol=1e-2) 193 | np.testing.assert_allclose(line3.terminals[1].power, 100, rtol=1e-2) 194 | 195 | np.testing.assert_allclose(net1.price, 89.9965, rtol=1e-2) 196 | np.testing.assert_allclose(net2.price, 89.9965, rtol=1e-2) 197 | np.testing.assert_allclose(net3.price, 2.2009, rtol=1e-2) 198 | 199 | 200 | T = 10 201 | p_load = (np.sin(np.pi * np.arange(T) / T) + 1e-2).reshape(-1, 1) 202 | 203 | 204 | class DynamicTest(unittest.TestCase): 205 | def test_dynamic_load(self): 206 | load = FixedLoad(power=p_load) 207 | gen = Generator(power_max=2, power_min=-0.01, alpha=100, beta=100) 208 | net = Net([load.terminals[0], gen.terminals[0]]) 209 | network = Group([load, gen], [net]) 210 | 211 | network.init_problem(time_horizon=T) 212 | network.problem.solve(solver="ECOS") 213 | np.testing.assert_allclose(load.terminals[0].power, p_load, atol=1e-4) 214 | np.testing.assert_allclose(gen.terminals[0].power, -p_load, atol=1e-4) 215 | np.testing.assert_allclose(net.price, p_load * 200 + 100, rtol=1e-2) 216 | 217 | def test_storage(self): 218 | load = FixedLoad(power=p_load) 219 | gen = Generator(power_max=2, alpha=100, beta=100) 220 | storage = Storage(charge_max=0.1, discharge_max=0.1, energy_max=0.5) 221 | 222 | net = Net([load.terminals[0], gen.terminals[0], storage.terminals[0]]) 223 | network = Group([load, gen, storage], [net]) 224 | network.init_problem(time_horizon=T) 225 | network.problem.solve(solver="ECOS") 226 | 227 | def test_deferrable_load(self): 228 | load = FixedLoad(power=p_load) 229 | gen = Generator(power_max=2, alpha=100, beta=100) 230 | deferrable = DeferrableLoad(time_start=5, energy=0.5, power_max=0.1) 231 | 232 | net = Net([load.terminals[0], gen.terminals[0], deferrable.terminals[0]]) 233 | network = Group([load, gen, deferrable], [net]) 234 | network.init_problem(time_horizon=T) 235 | network.problem.solve(solver="ECOS") 236 | 237 | def test_thermal_load(self): 238 | temp_amb = (np.sin(np.pi * np.arange(T) / T) + 1e-2).reshape( 239 | -1, 1 240 | ) ** 2 * 50 + 50 241 | 242 | load = FixedLoad(power=p_load) 243 | gen = Generator(power_max=2, alpha=100, beta=100) 244 | thermal = ThermalLoad( 245 | temp_init=60, 246 | temp_amb=temp_amb, 247 | temp_max=90, 248 | power_max=0.1, 249 | amb_conduct_coeff=0.1, 250 | efficiency=0.95, 251 | capacity=1, 252 | ) 253 | 254 | net = Net([load.terminals[0], gen.terminals[0], thermal.terminals[0]]) 255 | network = Group([load, gen, thermal], [net]) 256 | network.init_problem(time_horizon=T) 257 | network.problem.solve(solver="ECOS") 258 | 259 | 260 | T = 10 261 | K = 2 262 | p_load0 = (np.sin(np.pi * np.arange(T) / T) + 1e-2).reshape(-1, 1) 263 | p_load1 = np.abs((np.cos(np.pi * np.arange(T) / T) + 1e-2)).reshape(-1, 1) 264 | p_load_robust = np.hstack([p_load0, p_load1]) 265 | p_load_robust[0, 1] = p_load_robust[0, 0] 266 | 267 | 268 | class ScenarioTest(unittest.TestCase): 269 | def test_dynamic_load(self): 270 | load = FixedLoad(power=p_load_robust) 271 | gen = Generator(power_max=2, power_min=-0.01, alpha=100, beta=100) 272 | net = Net([load.terminals[0], gen.terminals[0]]) 273 | network = Group([load, gen], [net]) 274 | 275 | network.init_problem(time_horizon=T, num_scenarios=K) 276 | network.problem.solve(solver="ECOS") 277 | np.testing.assert_allclose(load.terminals[0].power, p_load_robust, atol=1e-4) 278 | np.testing.assert_allclose(gen.terminals[0].power, -p_load_robust, atol=1e-4) 279 | np.testing.assert_allclose( 280 | net.price[:, 0], p_load_robust[:, 0] * 200 + 100, rtol=1e-2 281 | ) 282 | 283 | def test_storage(self): 284 | load = FixedLoad(power=p_load_robust) 285 | gen = Generator(power_max=2, alpha=100, beta=100) 286 | storage = Storage(charge_max=0.1, discharge_max=0.1, energy_max=0.5) 287 | 288 | net = Net([load.terminals[0], gen.terminals[0], storage.terminals[0]]) 289 | network = Group([load, gen, storage], [net]) 290 | network.init_problem(time_horizon=T, num_scenarios=K) 291 | network.problem.solve(solver="ECOS") 292 | 293 | def test_deferrable_load(self): 294 | load = FixedLoad(power=p_load_robust) 295 | gen = Generator(power_max=2, alpha=100, beta=100) 296 | deferrable = DeferrableLoad(time_start=5, energy=0.5, power_max=0.1) 297 | 298 | net = Net([load.terminals[0], gen.terminals[0], deferrable.terminals[0]]) 299 | network = Group([load, gen, deferrable], [net]) 300 | network.init_problem(time_horizon=T, num_scenarios=K) 301 | network.problem.solve(solver="ECOS") 302 | 303 | def test_thermal_load(self): 304 | temp_amb = (np.sin(np.pi * np.arange(T) / T) + 1e-2).reshape( 305 | -1, 1 306 | ) ** 2 * 50 + 50 307 | 308 | load = FixedLoad(power=p_load_robust) 309 | gen = Generator(power_max=2, alpha=100, beta=100) 310 | thermal = ThermalLoad( 311 | temp_init=60, 312 | temp_amb=temp_amb, 313 | temp_max=90, 314 | power_max=0.1, 315 | amb_conduct_coeff=0.1, 316 | efficiency=0.95, 317 | capacity=1, 318 | ) 319 | 320 | net = Net([load.terminals[0], gen.terminals[0], thermal.terminals[0]]) 321 | network = Group([load, gen, thermal], [net]) 322 | network.init_problem(time_horizon=T, num_scenarios=K) 323 | network.problem.solve(solver="ECOS") 324 | 325 | 326 | # 327 | # TODO(mwytock): MPC test cases 328 | # 329 | 330 | if __name__ == "__main__": 331 | unittest.main() 332 | -------------------------------------------------------------------------------- /cvxpower/network.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Matt Wytock, Enzo Busseti, Nicholas Moehle, 2016-2019. 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Code written by Nicholas Moehle before January 2019 is licensed 16 | under the Apache License, Version 2.0 (the "License"); 17 | you may not use this file except in compliance with the License. 18 | You may obtain a copy of the License at 19 | 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | 22 | Unless required by applicable law or agreed to in writing, software 23 | distributed under the License is distributed on an "AS IS" BASIS, 24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | See the License for the specific language governing permissions and 26 | limitations under the License. 27 | """ 28 | 29 | 30 | __doc__ = """Network of power devices.""" 31 | 32 | import cvxpy as cvx 33 | import numpy as np 34 | import tqdm 35 | 36 | 37 | def _get_all_terminals(device): 38 | """Gets all terminals, including those nested within child devices.""" 39 | terms = device.terminals 40 | if hasattr(device, "internal_terminal"): 41 | terms += [device.internal_terminal] 42 | if hasattr(device, "devices"): 43 | terms += [t for d in device.devices for t in _get_all_terminals(d)] 44 | return terms 45 | 46 | 47 | class Terminal(object): 48 | """Device terminal.""" 49 | 50 | @property 51 | def power_var(self): 52 | return self._power 53 | 54 | @property 55 | def power(self): 56 | """Power consumed (positive value) or produced (negative value) at this 57 | terminal.""" 58 | return self._power.value 59 | 60 | def _init_problem(self, time_horizon, num_scenarios): 61 | self._power = cvx.Variable(shape=(time_horizon, num_scenarios)) 62 | 63 | def _set_payments(self, price): 64 | if price is not None and self._power.value is not None: 65 | self.payment = price * self._power.value 66 | else: 67 | self.payment = None 68 | 69 | 70 | class Net(object): 71 | r"""Connection point for device terminals. 72 | 73 | A net defines the power balance constraint ensuring that power sums to zero 74 | across all connected terminals at each time point. 75 | 76 | :param terminals: The terminals connected to this net 77 | :param name: (optional) Display name of net 78 | :type terminals: list of :class:`Terminal` 79 | :type name: string 80 | """ 81 | 82 | def __init__(self, terminals, name=None): 83 | self.name = "Net" if name is None else name 84 | self.terminals = terminals 85 | 86 | def _init_problem(self, time_horizon, num_scenarios): 87 | self.num_scenarios = num_scenarios 88 | # self.constraints = [sum(t._power[:, k] for t in self.terminals) == 0 89 | # for k in range(num_scenarios)] 90 | self.constraints = [sum(t._power for t in self.terminals) / num_scenarios == 0] 91 | 92 | self.problem = cvx.Problem(cvx.Minimize(0), self.constraints) 93 | 94 | def _set_payments(self): 95 | for t in self.terminals: 96 | t._set_payments(self.price) 97 | 98 | @property 99 | def results(self): 100 | return Results(price={self: self.price}) 101 | 102 | @property 103 | def price(self): 104 | """Price associated with this net.""" 105 | return self.constraints[0].dual_value 106 | # print([c.dual_value for c in self.constraints]) 107 | # raise 108 | # if (len(self.constraints) == 1 and 109 | # np.size(self.constraints[0].dual_value)) == 1: 110 | # return self.constraints[0].dual_value 111 | # TODO(enzo) hardcoded 1/K probability 112 | # return np.sum(constr.dual_value 113 | # for constr in self.constraints) 114 | # if self.num_scenarios > 1: 115 | # return np.matrix(np.sum([constr.dual_value[0] 116 | # for constr in self.constraints], 0)) 117 | # return np.hstack(constr.dual_value.reshape(-1, 1) 118 | # for constr in self.constraints) 119 | 120 | 121 | class Device(object): 122 | """Base class for network device. 123 | 124 | Subclasses are expected to override :attr:`constraints` and/or 125 | :attr:`cost` to define the device-specific cost function. 126 | 127 | :param terminals: The terminals of the device 128 | :param name: (optional) Display name of device 129 | :type terminals: list of :class:`Terminal` 130 | :type name: string 131 | """ 132 | 133 | def __init__(self, terminals, name=None): 134 | self.name = type(self).__name__ if name is None else name 135 | self.terminals = terminals 136 | self.problem = None 137 | 138 | @property 139 | def cost(self): 140 | """Device objective, to be overriden by subclasses. 141 | 142 | :rtype: cvxpy expression of size :math:`T \times K` 143 | """ 144 | return np.zeros((1, 1)) 145 | 146 | @property 147 | def constraints(self): 148 | """Device constraints, to be overriden by subclasses. 149 | 150 | :rtype: list of cvxpy constraints 151 | """ 152 | return [] 153 | 154 | @property 155 | def results(self): 156 | """Network optimization results. 157 | 158 | :rtype: :class:`Results` 159 | """ 160 | status = self.problem.status if self.problem else None 161 | return Results( 162 | power={(self, i): t.power for i, t in enumerate(self.terminals)}, 163 | payments={(self, i): t.payment for i, t in enumerate(self.terminals)}, 164 | status=status, 165 | ) 166 | 167 | def _init_problem(self, time_horizon, num_scenarios): 168 | self.problem = cvx.Problem( 169 | cvx.Minimize(cvx.sum(self.cost) / num_scenarios), 170 | # TODO(enzo) we should weight by probs 171 | self.constraints 172 | + [ 173 | terminal._power[0, k] == terminal._power[0, 0] 174 | for terminal in self.terminals 175 | for k in range( 176 | 1, terminal._power.shape[1] if len(terminal._power.shape) > 1 else 0 177 | ) 178 | ], 179 | ) 180 | 181 | def init_problem(self, time_horizon=1, num_scenarios=1): 182 | """Initialize the network optimization problem. 183 | 184 | :param time_horizon: The time horizon :math:`T` to optimize over. 185 | :param num_scenarios: The number of scenarios for robust MPC. 186 | :type time_horizon: int 187 | :type num_scenarios: int 188 | """ 189 | for terminal in _get_all_terminals(self): 190 | terminal._init_problem(time_horizon, num_scenarios) 191 | 192 | self._init_problem(time_horizon, num_scenarios) 193 | 194 | def optimize(self, time_horizon=1, num_scenarios=1, **kwargs): 195 | self.init_problem(time_horizon, num_scenarios) 196 | self.problem.solve(**kwargs) 197 | return self.results 198 | 199 | 200 | class Group(Device): 201 | """A single device composed of multiple devices and nets. 202 | 203 | 204 | The `Group` device allows for creating new devices composed of existing base 205 | devices or other groups. 206 | 207 | :param devices: Internal devices to be included. 208 | :param nets: Internal nets to be included. 209 | :param terminals: (optional) Terminals for new device. 210 | :param name: (optional) Display name of group device 211 | :type devices: list of :class:`Device` 212 | :type nets: list of :class:`Net` 213 | :type terminals: list of :class:`Terminal` 214 | :type name: string 215 | """ 216 | 217 | def __init__(self, devices, nets, terminals=[], name=None): 218 | super(Group, self).__init__(terminals, name) 219 | self.devices = devices 220 | self.nets = nets 221 | 222 | @property 223 | def results(self): 224 | for n in self.nets: 225 | n._set_payments() 226 | results = sum(x.results for x in self.devices + self.nets) 227 | results.status = self.problem.status if self.problem else None 228 | return results 229 | 230 | def _init_problem(self, time_horizon, num_scenarios): 231 | for device in self.devices: 232 | device._init_problem(time_horizon, num_scenarios) 233 | 234 | for net in self.nets: 235 | net._init_problem(time_horizon, num_scenarios) 236 | 237 | self.problem = sum(x.problem for x in self.devices + self.nets) 238 | 239 | # def optimize(self, **kwargs): 240 | # super(Group, self).optimize(**kwargs) 241 | # for n in self.nets: 242 | # n._set_payments 243 | # raise 244 | 245 | 246 | class Results(object): 247 | """Network optimization results.""" 248 | 249 | def __init__(self, power=None, payments=None, price=None, status=None): 250 | self.power = power if power else {} 251 | self.payments = payments if payments else {} 252 | self.price = price if price else {} 253 | self.status = status 254 | 255 | def __radd__(self, other): 256 | return self.__add__(other) 257 | 258 | def __add__(self, other): 259 | if other == 0: 260 | return self 261 | power = self.power.copy() 262 | payments = self.payments.copy() 263 | price = self.price.copy() 264 | 265 | power.update(other.power) 266 | payments.update(other.payments) 267 | price.update(other.price) 268 | status = self.status if self.status is not None else other.status 269 | return Results(power, payments, price, status) 270 | 271 | def __str__(self): 272 | return self.summary() 273 | 274 | def __repr__(self): 275 | return self.summary() 276 | 277 | def summary(self): 278 | """Summary of results. Only works for single period optimization. 279 | 280 | :rtype: str 281 | """ 282 | 283 | retval = "Status: " + self.status if self.status else "none" 284 | if not self.status in {cvx.OPTIMAL, cvx.OPTIMAL_INACCURATE}: 285 | return retval 286 | 287 | retval += "\n" 288 | retval += "%-20s %10s\n" % ("Terminal", "Power") 289 | retval += "%-20s %10s\n" % ("--------", "-----") 290 | averages = False 291 | for device_terminal, value in self.power.items(): 292 | label = "%s[%d]" % (device_terminal[0].name, device_terminal[1]) 293 | if isinstance(value, np.ndarray): 294 | value = np.mean(value) 295 | averages = True 296 | retval += "%-20s %10.2f\n" % (label, value) 297 | 298 | retval += "\n" 299 | retval += "%-20s %10s\n" % ("Net", "Price") 300 | retval += "%-20s %10s\n" % ("---", "-----") 301 | for net, value in self.price.items(): 302 | if isinstance(value, np.ndarray): 303 | value = np.mean(value) 304 | retval += "%-20s %10.4f\n" % (net.name, value) 305 | 306 | retval += "\n" 307 | retval += "%-20s %10s\n" % ("Device", "Payment") 308 | retval += "%-20s %10s\n" % ("------", "-------") 309 | device_payments = {d[0][0]: 0 for d in self.payments.items()} 310 | for device_terminal, value in self.payments.items(): 311 | if isinstance(value, np.ndarray): 312 | value = np.sum(value) 313 | device_payments[device_terminal[0]] += value 314 | for d in device_payments.keys(): 315 | retval += "%-20s %10.2f\n" % (d.name, device_payments[d]) 316 | 317 | if averages: 318 | retval += "\nPower and price are averages over the time horizon. Payment is total.\n" 319 | 320 | return retval 321 | 322 | def plot(self, index=None, **kwargs): # , print_terminals=True): 323 | """Plot results.""" 324 | import matplotlib.pyplot as plt 325 | 326 | fig, ax = plt.subplots(nrows=2, ncols=1, **kwargs) 327 | 328 | ax[0].set_ylabel("power") 329 | for device_terminal, value in self.power.items(): 330 | label = "%s[%d]" % (device_terminal[0].name, device_terminal[1]) 331 | if index is None: 332 | ax[0].plot(value, label=label) 333 | else: 334 | ax[0].plot(index, value, label=label) 335 | ax[0].legend(loc="best") 336 | 337 | ax[1].set_ylabel("price") 338 | for net, value in self.price.items(): 339 | if index is None: 340 | ax[1].plot(value, label=net.name) 341 | else: 342 | ax[1].plot(index, value, label=net.name) 343 | ax[1].legend(loc="best") 344 | 345 | return ax 346 | 347 | 348 | def _update_mpc_results(t, time_steps, results_t, results_mpc): 349 | for key, val in results_t.power.items(): 350 | results_mpc.power.setdefault(key, np.empty(time_steps))[t] = val[0, 0] 351 | for key, val in results_t.price.items(): 352 | results_mpc.price.setdefault(key, np.empty(time_steps))[t] = val[0, 0] 353 | for key, val in results_t.payments.items(): 354 | results_mpc.payments.setdefault(key, np.empty(time_steps))[t] = val[0, 0] 355 | 356 | 357 | class OptimizationError(Exception): 358 | """Error due to infeasibility or numerical problems during optimization.""" 359 | 360 | pass 361 | 362 | 363 | def run_mpc(device, time_steps, predict, execute, **kwargs): 364 | """Execute model predictive control. 365 | 366 | This method executes the model predictive control loop, roughly: 367 | 368 | .. code:: python 369 | 370 | for t in time_steps: 371 | predict(t) 372 | device.problem.solve() 373 | execute(t) 374 | .. 375 | 376 | It is the responsibility of the provided `predict` and `execute` functions 377 | to update the device models with the desired predictions and execute the 378 | actions as appropriate. 379 | 380 | :param device: Device (or network of devices) to optimize 381 | :param time_steps: Time steps to optimize over 382 | :param predict: Prediction step 383 | :param execute: Execution step 384 | :type device: :class:`Device` 385 | :type time_steps: sequence 386 | :type predict: single argument function 387 | :type execute: single argument function 388 | :returns: Model predictive control results 389 | :rtype: :class:`Results` 390 | :raise: :class:`OptimizationError` 391 | 392 | """ 393 | total_cost = 0.0 394 | results = Results() 395 | # T_MPC = device 396 | for t in tqdm.trange(time_steps): 397 | predict(t) 398 | 399 | # device.init_problem(time_horizon=1) 400 | device.problem.solve(**kwargs) 401 | if device.problem.status != cvx.OPTIMAL: 402 | # temporary 403 | raise OptimizationError( 404 | "failed at iteration %d, %s" % (t, device.problem.status) 405 | ) 406 | stage_cost = sum([device.cost[0, 0] for device in device.devices]).value 407 | # print('at time %s, adding cost %f' % (t, stage_cost)) 408 | total_cost += stage_cost 409 | execute(t) 410 | _update_mpc_results(t, time_steps, device.results, results) 411 | return total_cost, results 412 | -------------------------------------------------------------------------------- /cvxpower/network_test.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright (C) Matt Wytock, Enzo Busseti, Nicholas Moehle, 2016-2019. 3 | 4 | This program is free software: you can redistribute it and/or modify 5 | it under the terms of the GNU General Public License as published by 6 | the Free Software Foundation, either version 3 of the License, or 7 | (at your option) any later version. 8 | This program is distributed in the hope that it will be useful, 9 | but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | GNU General Public License for more details. 12 | You should have received a copy of the GNU General Public License 13 | along with this program. If not, see . 14 | 15 | Code written by Nicholas Moehle before January 2019 is licensed 16 | under the Apache License, Version 2.0 (the "License"); 17 | you may not use this file except in compliance with the License. 18 | You may obtain a copy of the License at 19 | 20 | http://www.apache.org/licenses/LICENSE-2.0 21 | 22 | Unless required by applicable law or agreed to in writing, software 23 | distributed under the License is distributed on an "AS IS" BASIS, 24 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 25 | See the License for the specific language governing permissions and 26 | limitations under the License. 27 | """ 28 | 29 | 30 | import unittest 31 | 32 | from cvxpower import * 33 | 34 | 35 | class ResultsTest(unittest.TestCase): 36 | def test_summary_normal(self): 37 | load = FixedLoad(power=10) 38 | gen = Generator(power_max=10) 39 | net = Net([load.terminals[0], gen.terminals[0]]) 40 | network = Group([load, gen], [net]) 41 | network.init_problem() 42 | network.problem.solve(solver="ECOS") 43 | assert "optimal" in network.results.summary() 44 | 45 | def test_summary_infeasible(self): 46 | load = FixedLoad(power=10) 47 | gen = Generator(power_max=1) 48 | net = Net([load.terminals[0], gen.terminals[0]]) 49 | network = Group([load, gen], [net]) 50 | network.init_problem() 51 | network.problem.solve(solver="ECOS") 52 | assert "infeasible" in network.results.summary() 53 | 54 | 55 | class DeviceTest(unittest.TestCase): 56 | def test_optimize(self): 57 | load = FixedLoad(power=1) 58 | gen = Generator(power_max=10) 59 | net = Net([load.terminals[0], gen.terminals[0]]) 60 | network = Group([load, gen], [net]) 61 | network.optimize(solver="ECOS") 62 | 63 | 64 | if __name__ == "__main__": 65 | unittest.main() 66 | -------------------------------------------------------------------------------- /docs/Makefile: -------------------------------------------------------------------------------- 1 | # Makefile for Sphinx documentation 2 | # 3 | 4 | # You can set these variables from the command line. 5 | SPHINXOPTS = 6 | SPHINXBUILD = sphinx-build 7 | PAPER = 8 | BUILDDIR = _build 9 | 10 | # User-friendly check for sphinx-build 11 | ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) 12 | $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) 13 | endif 14 | 15 | # Internal variables. 16 | PAPEROPT_a4 = -D latex_paper_size=a4 17 | PAPEROPT_letter = -D latex_paper_size=letter 18 | ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 19 | # the i18n builder cannot share the environment and doctrees with the others 20 | I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . 21 | 22 | .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest coverage gettext 23 | 24 | help: 25 | @echo "Please use \`make ' where is one of" 26 | @echo " html to make standalone HTML files" 27 | @echo " dirhtml to make HTML files named index.html in directories" 28 | @echo " singlehtml to make a single large HTML file" 29 | @echo " pickle to make pickle files" 30 | @echo " json to make JSON files" 31 | @echo " htmlhelp to make HTML files and a HTML help project" 32 | @echo " qthelp to make HTML files and a qthelp project" 33 | @echo " applehelp to make an Apple Help Book" 34 | @echo " devhelp to make HTML files and a Devhelp project" 35 | @echo " epub to make an epub" 36 | @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" 37 | @echo " latexpdf to make LaTeX files and run them through pdflatex" 38 | @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" 39 | @echo " text to make text files" 40 | @echo " man to make manual pages" 41 | @echo " texinfo to make Texinfo files" 42 | @echo " info to make Texinfo files and run them through makeinfo" 43 | @echo " gettext to make PO message catalogs" 44 | @echo " changes to make an overview of all changed/added/deprecated items" 45 | @echo " xml to make Docutils-native XML files" 46 | @echo " pseudoxml to make pseudoxml-XML files for display purposes" 47 | @echo " linkcheck to check all external links for integrity" 48 | @echo " doctest to run all doctests embedded in the documentation (if enabled)" 49 | @echo " coverage to run coverage check of the documentation (if enabled)" 50 | 51 | clean: 52 | rm -rf $(BUILDDIR)/* 53 | 54 | html: 55 | $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html 56 | @echo 57 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." 58 | 59 | dirhtml: 60 | $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml 61 | @echo 62 | @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." 63 | 64 | singlehtml: 65 | $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml 66 | @echo 67 | @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." 68 | 69 | pickle: 70 | $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle 71 | @echo 72 | @echo "Build finished; now you can process the pickle files." 73 | 74 | json: 75 | $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json 76 | @echo 77 | @echo "Build finished; now you can process the JSON files." 78 | 79 | htmlhelp: 80 | $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp 81 | @echo 82 | @echo "Build finished; now you can run HTML Help Workshop with the" \ 83 | ".hhp project file in $(BUILDDIR)/htmlhelp." 84 | 85 | qthelp: 86 | $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp 87 | @echo 88 | @echo "Build finished; now you can run "qcollectiongenerator" with the" \ 89 | ".qhcp project file in $(BUILDDIR)/qthelp, like this:" 90 | @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/DynamicEnergyManagement.qhcp" 91 | @echo "To view the help file:" 92 | @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/DynamicEnergyManagement.qhc" 93 | 94 | applehelp: 95 | $(SPHINXBUILD) -b applehelp $(ALLSPHINXOPTS) $(BUILDDIR)/applehelp 96 | @echo 97 | @echo "Build finished. The help book is in $(BUILDDIR)/applehelp." 98 | @echo "N.B. You won't be able to view it unless you put it in" \ 99 | "~/Library/Documentation/Help or install it in your application" \ 100 | "bundle." 101 | 102 | devhelp: 103 | $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp 104 | @echo 105 | @echo "Build finished." 106 | @echo "To view the help file:" 107 | @echo "# mkdir -p $$HOME/.local/share/devhelp/DynamicEnergyManagement" 108 | @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/DynamicEnergyManagement" 109 | @echo "# devhelp" 110 | 111 | epub: 112 | $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub 113 | @echo 114 | @echo "Build finished. The epub file is in $(BUILDDIR)/epub." 115 | 116 | latex: 117 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 118 | @echo 119 | @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." 120 | @echo "Run \`make' in that directory to run these through (pdf)latex" \ 121 | "(use \`make latexpdf' here to do that automatically)." 122 | 123 | latexpdf: 124 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 125 | @echo "Running LaTeX files through pdflatex..." 126 | $(MAKE) -C $(BUILDDIR)/latex all-pdf 127 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 128 | 129 | latexpdfja: 130 | $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex 131 | @echo "Running LaTeX files through platex and dvipdfmx..." 132 | $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja 133 | @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." 134 | 135 | text: 136 | $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text 137 | @echo 138 | @echo "Build finished. The text files are in $(BUILDDIR)/text." 139 | 140 | man: 141 | $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man 142 | @echo 143 | @echo "Build finished. The manual pages are in $(BUILDDIR)/man." 144 | 145 | texinfo: 146 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 147 | @echo 148 | @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." 149 | @echo "Run \`make' in that directory to run these through makeinfo" \ 150 | "(use \`make info' here to do that automatically)." 151 | 152 | info: 153 | $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo 154 | @echo "Running Texinfo files through makeinfo..." 155 | make -C $(BUILDDIR)/texinfo info 156 | @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." 157 | 158 | gettext: 159 | $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale 160 | @echo 161 | @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." 162 | 163 | changes: 164 | $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes 165 | @echo 166 | @echo "The overview file is in $(BUILDDIR)/changes." 167 | 168 | linkcheck: 169 | $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck 170 | @echo 171 | @echo "Link check complete; look for any errors in the above output " \ 172 | "or in $(BUILDDIR)/linkcheck/output.txt." 173 | 174 | doctest: 175 | $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest 176 | @echo "Testing of doctests in the sources finished, look at the " \ 177 | "results in $(BUILDDIR)/doctest/output.txt." 178 | 179 | coverage: 180 | $(SPHINXBUILD) -b coverage $(ALLSPHINXOPTS) $(BUILDDIR)/coverage 181 | @echo "Testing of coverage in the sources finished, look at the " \ 182 | "results in $(BUILDDIR)/coverage/python.txt." 183 | 184 | xml: 185 | $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml 186 | @echo 187 | @echo "Build finished. The XML files are in $(BUILDDIR)/xml." 188 | 189 | pseudoxml: 190 | $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml 191 | @echo 192 | @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." 193 | -------------------------------------------------------------------------------- /docs/_static/dynamic_load_results.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/_static/dynamic_load_results.jpg -------------------------------------------------------------------------------- /docs/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # 3 | # Dynamic Energy Management documentation build configuration file, created by 4 | # sphinx-quickstart on Mon Aug 1 10:07:28 2016. 5 | # 6 | # This file is execfile()d with the current directory set to its 7 | # containing dir. 8 | # 9 | # Note that not all possible configuration values are present in this 10 | # autogenerated file. 11 | # 12 | # All configuration values have a default; values that are commented out 13 | # serve to show the default. 14 | 15 | import mock 16 | import os 17 | import shlex 18 | import sys 19 | 20 | # If extensions (or modules to document with autodoc) are in another directory, 21 | # add these directories to sys.path here. If the directory is relative to the 22 | # documentation root, use os.path.abspath to make it absolute, like shown here. 23 | sys.path.insert(0, os.path.abspath("..")) 24 | 25 | # Mock out dependencies for readthedocs 26 | MOCK_MODULES = ["cvxpy", "matplotlib", "matplotlib.pyplot", "numpy", "tqdm"] 27 | for mod_name in MOCK_MODULES: 28 | sys.modules[mod_name] = mock.Mock() 29 | 30 | # -- General configuration ------------------------------------------------ 31 | 32 | # If your documentation needs a minimal Sphinx version, state it here. 33 | # needs_sphinx = '1.0' 34 | 35 | # Add any Sphinx extension module names here, as strings. They can be 36 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 37 | # ones. 38 | extensions = [ 39 | "sphinx.ext.autodoc", 40 | "sphinx.ext.mathjax", 41 | ] 42 | 43 | # Add any paths that contain templates here, relative to this directory. 44 | templates_path = ["_templates"] 45 | 46 | # The suffix(es) of source filenames. 47 | # You can specify multiple suffix as a list of string: 48 | # source_suffix = ['.rst', '.md'] 49 | source_suffix = ".rst" 50 | 51 | # The encoding of source files. 52 | # source_encoding = 'utf-8-sig' 53 | 54 | # The master toctree document. 55 | master_doc = "index" 56 | 57 | # General information about the project. 58 | project = u"DEM" 59 | copyright = u"2016, Matt Wytock, Stephen Boyd" 60 | author = u"Matt Wytock, Stephen Boyd" 61 | 62 | # The version info for the project you're documenting, acts as replacement for 63 | # |version| and |release|, also used in various other places throughout the 64 | # built documents. 65 | # 66 | # The short X.Y version. 67 | version = u"0.0.1" 68 | # The full version, including alpha/beta/rc tags. 69 | release = u"0.0.1" 70 | 71 | # The language for content autogenerated by Sphinx. Refer to documentation 72 | # for a list of supported languages. 73 | # 74 | # This is also used if you do content translation via gettext catalogs. 75 | # Usually you set "language" from the command line for these cases. 76 | language = None 77 | 78 | # There are two options for replacing |today|: either, you set today to some 79 | # non-false value, then it is used: 80 | # today = '' 81 | # Else, today_fmt is used as the format for a strftime call. 82 | # today_fmt = '%B %d, %Y' 83 | 84 | # List of patterns, relative to source directory, that match files and 85 | # directories to ignore when looking for source files. 86 | exclude_patterns = ["_build"] 87 | 88 | # The reST default role (used for this markup: `text`) to use for all 89 | # documents. 90 | # default_role = None 91 | 92 | # If true, '()' will be appended to :func: etc. cross-reference text. 93 | # add_function_parentheses = True 94 | 95 | # If true, the current module name will be prepended to all description 96 | # unit titles (such as .. function::). 97 | # add_module_names = True 98 | 99 | # If true, sectionauthor and moduleauthor directives will be shown in the 100 | # output. They are ignored by default. 101 | # show_authors = False 102 | 103 | # The name of the Pygments (syntax highlighting) style to use. 104 | pygments_style = "sphinx" 105 | 106 | # A list of ignored prefixes for module index sorting. 107 | # modindex_common_prefix = [] 108 | 109 | # If true, keep warnings as "system message" paragraphs in the built documents. 110 | # keep_warnings = False 111 | 112 | # If true, `todo` and `todoList` produce output, else they produce nothing. 113 | todo_include_todos = False 114 | 115 | 116 | # -- Options for HTML output ---------------------------------------------- 117 | 118 | # The theme to use for HTML and HTML Help pages. See the documentation for 119 | # a list of builtin themes. 120 | html_theme = "bizstyle" 121 | 122 | # Theme options are theme-specific and customize the look and feel of a theme 123 | # further. For a list of options available for each theme, see the 124 | # documentation. 125 | html_theme_options = { 126 | # 'github_banner': 'true', 127 | # 'github_repo': 'dem', 128 | # 'github_type': 'star', 129 | # 'github_user': 'mwytock', 130 | } 131 | 132 | # Add any paths that contain custom themes here, relative to this directory. 133 | # html_theme_path = [] 134 | 135 | # The name for this set of Sphinx documents. If None, it defaults to 136 | # " v documentation". 137 | # html_title = None 138 | 139 | # A shorter title for the navigation bar. Default is the same as html_title. 140 | # html_short_title = None 141 | 142 | # The name of an image file (relative to this directory) to place at the top 143 | # of the sidebar. 144 | # html_logo = None 145 | 146 | # The name of an image file (within the static path) to use as favicon of the 147 | # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 148 | # pixels large. 149 | # html_favicon = None 150 | 151 | # Add any paths that contain custom static files (such as style sheets) here, 152 | # relative to this directory. They are copied after the builtin static files, 153 | # so a file named "default.css" will overwrite the builtin "default.css". 154 | html_static_path = ["_static"] 155 | 156 | # Add any extra paths that contain custom files (such as robots.txt or 157 | # .htaccess) here, relative to this directory. These files are copied 158 | # directly to the root of the documentation. 159 | # html_extra_path = [] 160 | 161 | # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, 162 | # using the given strftime format. 163 | # html_last_updated_fmt = '%b %d, %Y' 164 | 165 | # If true, SmartyPants will be used to convert quotes and dashes to 166 | # typographically correct entities. 167 | # html_use_smartypants = True 168 | 169 | # Custom sidebar templates, maps document names to template names. 170 | # html_sidebars = { 171 | #'**': [ 172 | # 'about.html', 173 | # 'navigation.html', 174 | # ] 175 | # } 176 | 177 | # Additional templates that should be rendered to pages, maps page names to 178 | # template names. 179 | # html_additional_pages = {} 180 | 181 | # If false, no module index is generated. 182 | # html_domain_indices = True 183 | 184 | # If false, no index is generated. 185 | # html_use_index = True 186 | 187 | # If true, the index is split into individual pages for each letter. 188 | # html_split_index = False 189 | 190 | # If true, links to the reST sources are added to the pages. 191 | # html_show_sourcelink = True 192 | 193 | # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. 194 | # html_show_sphinx = True 195 | 196 | # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. 197 | # html_show_copyright = True 198 | 199 | # If true, an OpenSearch description file will be output, and all pages will 200 | # contain a tag referring to it. The value of this option must be the 201 | # base URL from which the finished HTML is served. 202 | # html_use_opensearch = '' 203 | 204 | # This is the file name suffix for HTML files (e.g. ".xhtml"). 205 | # html_file_suffix = None 206 | 207 | # Language to be used for generating the HTML full-text search index. 208 | # Sphinx supports the following languages: 209 | # 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' 210 | # 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' 211 | # html_search_language = 'en' 212 | 213 | # A dictionary with options for the search language support, empty by default. 214 | # Now only 'ja' uses this config value 215 | # html_search_options = {'type': 'default'} 216 | 217 | # The name of a javascript file (relative to the configuration directory) that 218 | # implements a search results scorer. If empty, the default will be used. 219 | # html_search_scorer = 'scorer.js' 220 | 221 | # Output file base name for HTML help builder. 222 | htmlhelp_basename = "DynamicEnergyManagementdoc" 223 | 224 | # -- Options for LaTeX output --------------------------------------------- 225 | 226 | latex_elements = { 227 | # The paper size ('letterpaper' or 'a4paper'). 228 | #'papersize': 'letterpaper', 229 | # The font size ('10pt', '11pt' or '12pt'). 230 | #'pointsize': '10pt', 231 | # Additional stuff for the LaTeX preamble. 232 | #'preamble': '', 233 | # Latex figure (float) alignment 234 | #'figure_align': 'htbp', 235 | } 236 | 237 | # Grouping the document tree into LaTeX files. List of tuples 238 | # (source start file, target name, title, 239 | # author, documentclass [howto, manual, or own class]). 240 | latex_documents = [ 241 | ( 242 | master_doc, 243 | "DynamicEnergyManagement.tex", 244 | u"Dynamic Energy Management Documentation", 245 | u"Matt Wytock, Stephen Boyd", 246 | "manual", 247 | ), 248 | ] 249 | 250 | # The name of an image file (relative to this directory) to place at the top of 251 | # the title page. 252 | # latex_logo = None 253 | 254 | # For "manual" documents, if this is true, then toplevel headings are parts, 255 | # not chapters. 256 | # latex_use_parts = False 257 | 258 | # If true, show page references after internal links. 259 | # latex_show_pagerefs = False 260 | 261 | # If true, show URL addresses after external links. 262 | # latex_show_urls = False 263 | 264 | # Documents to append as an appendix to all manuals. 265 | # latex_appendices = [] 266 | 267 | # If false, no module index is generated. 268 | # latex_domain_indices = True 269 | 270 | 271 | # -- Options for manual page output --------------------------------------- 272 | 273 | # One entry per manual page. List of tuples 274 | # (source start file, name, description, authors, manual section). 275 | man_pages = [ 276 | ( 277 | master_doc, 278 | "dynamicenergymanagement", 279 | u"Dynamic Energy Management Documentation", 280 | [author], 281 | 1, 282 | ) 283 | ] 284 | 285 | # If true, show URL addresses after external links. 286 | # man_show_urls = False 287 | 288 | 289 | # -- Options for Texinfo output ------------------------------------------- 290 | 291 | # Grouping the document tree into Texinfo files. List of tuples 292 | # (source start file, target name, title, author, 293 | # dir menu entry, description, category) 294 | texinfo_documents = [ 295 | ( 296 | master_doc, 297 | "DynamicEnergyManagement", 298 | u"Dynamic Energy Management Documentation", 299 | author, 300 | "DynamicEnergyManagement", 301 | "One line description of project.", 302 | "Miscellaneous", 303 | ), 304 | ] 305 | 306 | # Documents to append as an appendix to all manuals. 307 | # texinfo_appendices = [] 308 | 309 | # If false, no module index is generated. 310 | # texinfo_domain_indices = True 311 | 312 | # How to display URL addresses: 'footnote', 'no', or 'inline'. 313 | # texinfo_show_urls = 'footnote' 314 | 315 | # If true, do not generate a @detailmenu in the "Top" node's menu. 316 | # texinfo_no_detailmenu = False 317 | 318 | autodoc_default_flags = [ 319 | "members", 320 | "show-inheritance", 321 | ] 322 | 323 | autodoc_member_order = "bysource" 324 | -------------------------------------------------------------------------------- /docs/devices.rst: -------------------------------------------------------------------------------- 1 | 2 | Devices 3 | ======= 4 | 5 | .. automodule:: dem.devices 6 | 7 | 8 | Generators 9 | ---------- 10 | 11 | .. autoclass:: dem.devices.Generator 12 | 13 | Loads 14 | ----- 15 | 16 | .. autoclass:: dem.devices.FixedLoad 17 | .. autoclass:: dem.devices.DeferrableLoad 18 | .. autoclass:: dem.devices.CurtailableLoad 19 | .. autoclass:: dem.devices.ThermalLoad 20 | 21 | Storage devices 22 | --------------- 23 | 24 | .. autoclass:: dem.devices.Storage 25 | 26 | 27 | Transmission lines 28 | ------------------ 29 | 30 | .. autoclass:: dem.devices.TransmissionLine 31 | -------------------------------------------------------------------------------- /docs/examples.rst: -------------------------------------------------------------------------------- 1 | Examples 2 | ======== 3 | 4 | .. toctree:: 5 | :maxdepth: 2 6 | 7 | notebooks/static 8 | notebooks/dynamic 9 | notebooks/windfarm 10 | notebooks/microgrid 11 | notebooks/smarthome 12 | -------------------------------------------------------------------------------- /docs/get_started.rst: -------------------------------------------------------------------------------- 1 | .. Dynamic Energy Management documentation master file, created by 2 | sphinx-quickstart on Mon Aug 1 10:07:28 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Get Started 7 | =========== 8 | 9 | Introduction 10 | ------------ 11 | 12 | Installation 13 | ------------ 14 | 15 | Basic Usage 16 | ----------- 17 | 18 | .. code:: python 19 | 20 | import numpy as np 21 | from dem import * 22 | 23 | T = 100 24 | p_load = (np.sin(np.pi*np.arange(T)/T)+1)*0.5 25 | 26 | load = FixedLoad(p=p_load) 27 | gen = Generator(p_max=2, p_min=-0.1, alpha=100, beta=100) 28 | net = Net([load.terminals[0], gen.terminals[0]]) 29 | network = Group([load, gen], [net]) 30 | 31 | network.init_problem(time_horizon=T) 32 | network.problem.solve() 33 | network.results.plot() 34 | .. 35 | 36 | .. image:: _static/dynamic_load_results.jpg 37 | -------------------------------------------------------------------------------- /docs/index.rst: -------------------------------------------------------------------------------- 1 | .. Dynamic Energy Management documentation master file, created by 2 | sphinx-quickstart on Mon Aug 1 10:07:28 2016. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Dynamic Energy Management 7 | ========================= 8 | 9 | .. toctree:: 10 | :maxdepth: 2 11 | 12 | get_started 13 | network 14 | devices 15 | examples 16 | -------------------------------------------------------------------------------- /docs/network.rst: -------------------------------------------------------------------------------- 1 | Network Model 2 | ============= 3 | 4 | .. automodule:: dem.network 5 | :members: 6 | -------------------------------------------------------------------------------- /docs/notebooks/dynamic.rst: -------------------------------------------------------------------------------- 1 | 2 | Dynamic power flow 3 | ================== 4 | 5 | .. code:: python 6 | 7 | %matplotlib inline 8 | import matplotlib.pyplot as plt 9 | import matplotlib 10 | import numpy as np 11 | 12 | from dem import * 13 | 14 | matplotlib.rc("figure", figsize=(16,6)) 15 | matplotlib.rc("lines", linewidth=2) 16 | 17 | T = 100 18 | p_load = np.sin(np.pi*np.arange(T)/T) 19 | 20 | Basic examples 21 | -------------- 22 | 23 | Time-varying load 24 | ~~~~~~~~~~~~~~~~~ 25 | 26 | .. code:: python 27 | 28 | load = FixedLoad(power=p_load) 29 | gen = Generator(power_max=2, power_min=-0.1, alpha=100, beta=100) 30 | net = Net([load.terminals[0], gen.terminals[0]]) 31 | network = Group([load, gen], [net]) 32 | 33 | network.init_problem(time_horizon=T) 34 | network.problem.solve() 35 | network.results.plot() 36 | 37 | 38 | 39 | 40 | .. parsed-literal:: 41 | 42 | array([, 43 | ], dtype=object) 44 | 45 | 46 | 47 | 48 | .. image:: dynamic_files/dynamic_4_1.png 49 | 50 | 51 | Storage 52 | ~~~~~~~ 53 | 54 | .. code:: python 55 | 56 | load = FixedLoad(power=p_load) 57 | gen = Generator(power_max=2, alpha=100, beta=100) 58 | storage = Storage(discharge_max=0.4, charge_max=0.1, energy_max=2) 59 | net = Net([load.terminals[0], gen.terminals[0], storage.terminals[0]]) 60 | network = Group([load, gen, storage], [net]) 61 | 62 | network.init_problem(time_horizon=T) 63 | network.problem.solve() 64 | network.results.plot() 65 | 66 | fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(16,3)) 67 | ax.plot(storage.energy.value) 68 | ax.set_ylabel("stored energy") 69 | 70 | 71 | 72 | 73 | .. parsed-literal:: 74 | 75 | 76 | 77 | 78 | 79 | 80 | .. image:: dynamic_files/dynamic_6_1.png 81 | 82 | 83 | 84 | .. image:: dynamic_files/dynamic_6_2.png 85 | 86 | 87 | Deferrable load 88 | ~~~~~~~~~~~~~~~ 89 | 90 | .. code:: python 91 | 92 | load = FixedLoad(power=p_load) 93 | gen = Generator(power_max=2, alpha=100, beta=100) 94 | deferrable = DeferrableLoad(time_start=50, energy=20, power_max=0.8) 95 | net = Net([load.terminals[0], gen.terminals[0], deferrable.terminals[0]]) 96 | network = Group([load, gen, deferrable], [net]) 97 | 98 | network.init_problem(time_horizon=T) 99 | network.problem.solve() 100 | network.results.plot() 101 | 102 | 103 | 104 | 105 | .. parsed-literal:: 106 | 107 | array([, 108 | ], dtype=object) 109 | 110 | 111 | 112 | 113 | .. image:: dynamic_files/dynamic_8_1.png 114 | 115 | 116 | Thermal load 117 | ~~~~~~~~~~~~ 118 | 119 | .. code:: python 120 | 121 | temp_amb = (np.sin(np.pi*np.arange(T)/T) + 1e-2).reshape(-1,1)**2*50+50 122 | 123 | load = FixedLoad(power=p_load) 124 | gen = Generator(power_max=2, alpha=100, beta=100) 125 | thermal = ThermalLoad( 126 | temp_init=60, temp_amb=temp_amb, temp_min=None, temp_max=80, 127 | power_max=2, amb_conduct_coeff=0.1, efficiency=3, capacity=1) 128 | net = Net([load.terminals[0], gen.terminals[0], thermal.terminals[0]]) 129 | network = Group([load, gen, thermal], [net]) 130 | 131 | network.init_problem(time_horizon=T) 132 | network.problem.solve() 133 | network.results.plot() 134 | 135 | fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(16,3)) 136 | ax.plot(temp_amb, label="Ambient") 137 | ax.plot(thermal.temp.value, label="Internal") 138 | ax.set_ylabel("temperature") 139 | ax.legend() 140 | 141 | 142 | 143 | 144 | .. parsed-literal:: 145 | 146 | 147 | 148 | 149 | 150 | 151 | .. image:: dynamic_files/dynamic_10_1.png 152 | 153 | 154 | 155 | .. image:: dynamic_files/dynamic_10_2.png 156 | 157 | -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_10_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_10_1.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_10_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_10_2.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_11_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_11_1.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_11_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_11_2.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_4_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_4_1.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_5_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_5_1.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_6_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_6_1.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_6_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_6_2.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_7_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_7_1.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_7_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_7_2.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_8_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_8_1.png -------------------------------------------------------------------------------- /docs/notebooks/dynamic_files/dynamic_9_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/dynamic_files/dynamic_9_1.png -------------------------------------------------------------------------------- /docs/notebooks/microgrid.rst: -------------------------------------------------------------------------------- 1 | 2 | Community microgrid 3 | =================== 4 | 5 | Coming soon! 6 | -------------------------------------------------------------------------------- /docs/notebooks/smarthome.rst: -------------------------------------------------------------------------------- 1 | 2 | Smart home 3 | ========== 4 | 5 | Coming soon! 6 | -------------------------------------------------------------------------------- /docs/notebooks/static.rst: -------------------------------------------------------------------------------- 1 | 2 | Static power flow 3 | ================= 4 | 5 | .. code:: python 6 | 7 | %matplotlib inline 8 | import matplotlib.pyplot as plt 9 | import matplotlib 10 | import numpy as np 11 | 12 | from dem import * 13 | 14 | matplotlib.rc("figure", figsize=(7,7)) 15 | matplotlib.rc("lines", linewidth=2) 16 | 17 | Basic examples 18 | -------------- 19 | 20 | Hello world 21 | ~~~~~~~~~~~ 22 | 23 | .. code:: python 24 | 25 | load = FixedLoad(power=100) 26 | gen = Generator(power_max=1000, alpha=0.01, beta=100) 27 | net = Net([load.terminals[0], gen.terminals[0]]) 28 | network = Group([load, gen], [net]) 29 | 30 | network.init_problem() 31 | network.problem.solve() 32 | print network.results.summary() 33 | 34 | 35 | .. parsed-literal:: 36 | 37 | Terminal Power 38 | -------- ----- 39 | FixedLoad[0] 100.0000 40 | Generator[0] -100.0000 41 | 42 | Net Price 43 | --- ----- 44 | Net 102.0001 45 | 46 | 47 | 48 | Curtailable load 49 | ~~~~~~~~~~~~~~~~ 50 | 51 | .. code:: python 52 | 53 | load = CurtailableLoad(power=1000, alpha=150) 54 | gen = Generator(power_max=1000, alpha=1, beta=100) 55 | net = Net([load.terminals[0], gen.terminals[0]]) 56 | network = Group([load, gen], [net]) 57 | 58 | network.init_problem() 59 | network.problem.solve() 60 | print network.results.summary() 61 | 62 | 63 | .. parsed-literal:: 64 | 65 | Terminal Power 66 | -------- ----- 67 | CurtailableLoad[0] 25.0026 68 | Generator[0] -25.0026 69 | 70 | Net Price 71 | --- ----- 72 | Net 150.0000 73 | 74 | 75 | 76 | Two generators, transmission line 77 | ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 78 | 79 | .. code:: python 80 | 81 | load = FixedLoad(power=100) 82 | gen1 = Generator(power_max=1000, alpha=0.01, beta=100, name="Gen1") 83 | gen2 = Generator(power_max=100, alpha=0.1, beta=0.1, name="Gen2") 84 | line = TransmissionLine(power_max=50) 85 | 86 | net1 = Net([load.terminals[0], gen1.terminals[0], line.terminals[0]]) 87 | net2 = Net([gen2.terminals[0], line.terminals[1]]) 88 | network = Group([load, gen1, gen2, line], [net1, net2]) 89 | 90 | network.init_problem() 91 | network.problem.solve() 92 | print network.results.summary() 93 | 94 | 95 | .. parsed-literal:: 96 | 97 | Terminal Power 98 | -------- ----- 99 | FixedLoad[0] 100.0000 100 | TransmissionLine[0] -50.0000 101 | TransmissionLine[1] 50.0000 102 | Gen2[0] -50.0000 103 | Gen1[0] -50.0000 104 | 105 | Net Price 106 | --- ----- 107 | Net 101.0000 108 | Net 10.1000 109 | 110 | 111 | 112 | Three buses 113 | ~~~~~~~~~~~ 114 | 115 | Figure 2.1 from Kraning, et al. without the battery. 116 | 117 | .. figure:: ./three_bus.png 118 | :alt: Three bus example 119 | 120 | Three bus example 121 | 122 | .. code:: python 123 | 124 | load1 = FixedLoad(power=50, name="Load1") 125 | load2 = FixedLoad(power=100, name="Load2") 126 | gen1 = Generator(power_max=1000, alpha=0.01, beta=100, name="Gen1") 127 | gen2 = Generator(power_max=100, alpha=0.1, beta=0.1, name="Gen2") 128 | line1 = TransmissionLine(power_max=50) 129 | line2 = TransmissionLine(power_max=10) 130 | line3 = TransmissionLine(power_max=50) 131 | 132 | net1 = Net([load1.terminals[0], gen1.terminals[0], line1.terminals[0], line2.terminals[0]]) 133 | net2 = Net([load2.terminals[0], line1.terminals[1], line3.terminals[0]]) 134 | net3 = Net([gen2.terminals[0], line2.terminals[1], line3.terminals[1]]) 135 | network = Group([load1, load2, gen1, gen2, line1, line2, line3], [net1, net2, net3]) 136 | 137 | network.init_problem() 138 | network.problem.solve() 139 | print network.results.summary() 140 | 141 | 142 | .. parsed-literal:: 143 | 144 | Terminal Power 145 | -------- ----- 146 | TransmissionLine[1] 50.0000 147 | TransmissionLine[0] -10.0000 148 | Gen2[0] -60.0000 149 | Gen1[0] -90.0000 150 | TransmissionLine[0] 50.0000 151 | TransmissionLine[1] -50.0000 152 | Load2[0] 100.0000 153 | Load1[0] 50.0000 154 | TransmissionLine[1] 10.0000 155 | TransmissionLine[0] -50.0000 156 | 157 | Net Price 158 | --- ----- 159 | Net 101.8008 160 | Net 196.1907 161 | Net 12.0975 162 | 163 | 164 | 165 | Grouping devices 166 | ---------------- 167 | 168 | We can wrap up several devices and nets into a single device 169 | 170 | .. code:: python 171 | 172 | solar = Generator(power_max=10, alpha=0, beta=0, name="Solar") 173 | load = FixedLoad(power=13) 174 | line = TransmissionLine(power_max=25) 175 | net = Net([load.terminals[0], solar.terminals[0], line.terminals[0]]) 176 | home = Group([solar, load, line], [net], [line.terminals[1]], name="Home") 177 | 178 | grid = Generator(power_max=1e6, alpha=0.05, beta=100, name="Grid") 179 | meter = Net([line.terminals[1], grid.terminals[0]], name="Meter") 180 | network = Group([home, grid], [meter]) 181 | 182 | network.init_problem() 183 | network.problem.solve() 184 | print network.results.summary() 185 | 186 | 187 | .. parsed-literal:: 188 | 189 | Terminal Power 190 | -------- ----- 191 | FixedLoad[0] 13.0000 192 | TransmissionLine[1] 3.0000 193 | Solar[0] -10.0000 194 | Grid[0] -3.0000 195 | TransmissionLine[0] -3.0000 196 | 197 | Net Price 198 | --- ----- 199 | Net 100.3000 200 | Meter 100.3000 201 | 202 | 203 | 204 | Varying parameters 205 | ------------------ 206 | 207 | We can modify a single parameter and reoptimize, which is useful for 208 | sweeping over a parameter range. 209 | 210 | .. code:: python 211 | 212 | load1 = FixedLoad(power=50, name="Load1") 213 | load2 = FixedLoad(power=100, name="Load2") 214 | gen1 = Generator(power_max=100, alpha=1, beta=10, name="Gen1") 215 | gen2 = Generator(power_max=1000, alpha=0.01, beta=0, name="Gen2") 216 | line1 = TransmissionLine(power_max=100) 217 | line2 = TransmissionLine(power_max=10) 218 | line3 = TransmissionLine(power_max=Parameter(1)) 219 | 220 | net1 = Net([load1.terminals[0], gen1.terminals[0], line1.terminals[0], line2.terminals[0]]) 221 | net2 = Net([load2.terminals[0], line1.terminals[1], line3.terminals[0]]) 222 | net3 = Net([gen2.terminals[0], line2.terminals[1], line3.terminals[1]]) 223 | network = Group([load1, load2, gen1, gen2, line1, line2, line3], [net1, net2, net3]) 224 | 225 | network.init_problem() 226 | xs = np.linspace(0, 150, 100) 227 | prices = np.empty((len(xs), 3)) 228 | for i, x in enumerate(xs): 229 | line3.power_max.value = x 230 | network.problem.solve() 231 | prices[i,:] = [net.price for net in network.nets] 232 | 233 | plt.plot(xs, prices) 234 | plt.xlabel("Line capacity") 235 | plt.ylabel("Price") 236 | plt.legend(["Bus 1", "Bus 2", "Bus 3"]) 237 | 238 | 239 | 240 | 241 | .. parsed-literal:: 242 | 243 | 244 | 245 | 246 | 247 | 248 | .. image:: static_files/static_14_1.png 249 | 250 | -------------------------------------------------------------------------------- /docs/notebooks/static_files/static_14_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/static_files/static_14_1.png -------------------------------------------------------------------------------- /docs/notebooks/three_bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/three_bus.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm.rst: -------------------------------------------------------------------------------- 1 | 2 | Wind farm integration 3 | ===================== 4 | 5 | An example of a wind farm offering firm power by estimating the expected 6 | wind resource and then using a MPC to jointly optimize control of a 7 | small gas turbine and storage. 8 | 9 | .. code:: python 10 | 11 | %matplotlib inline 12 | import datetime 13 | import pandas as pd 14 | import matplotlib 15 | import numpy as np 16 | import cvxpy as cvx 17 | from matplotlib import pyplot as plt 18 | 19 | from dem import * 20 | 21 | matplotlib.rc("figure", figsize=(16,6)) 22 | matplotlib.rc("lines", linewidth=2) 23 | 24 | Wind resource 25 | ------------- 26 | 27 | Data is from `NREL wind integration 28 | dataset `__, 29 | site 20182. 30 | 31 | .. code:: python 32 | 33 | def read_wind_csv(filename): 34 | df = pd.read_csv( 35 | filename, 36 | skiprows=3, 37 | parse_dates=[[0,1,2,3,4]], 38 | date_parser=lambda *cols: datetime.datetime(*map(int, cols)), 39 | index_col=0) 40 | df.index.name = "time" 41 | return df 42 | 43 | wind = pd.concat([ 44 | read_wind_csv("nrel_wind/20182-2011.csv"), 45 | read_wind_csv("nrel_wind/20182-2012.csv")]) 46 | p_wind = wind["power (MW)"].resample("15min").mean() 47 | p_wind["2012-01"].plot() 48 | 49 | 50 | 51 | 52 | .. parsed-literal:: 53 | 54 | 55 | 56 | 57 | 58 | 59 | .. image:: windfarm_files/windfarm_3_1.png 60 | 61 | 62 | Power blocks 63 | ~~~~~~~~~~~~ 64 | 65 | The amount of power offered for sale 66 | 67 | .. code:: python 68 | 69 | def interval_of_day(dt): 70 | return dt.hour*4 + dt.minute/15 71 | 72 | # compute target output 73 | p_wind_by_interval = p_wind.groupby(interval_of_day(p_wind.index)).mean() 74 | p_wind_mean = pd.Series([p_wind_by_interval[interval_of_day(x)] for x in p_wind.index], index=p_wind.index) 75 | 76 | p_wind_by_interval.plot() 77 | plt.ylim([0,16]) 78 | 79 | 80 | 81 | 82 | .. parsed-literal:: 83 | 84 | (0, 16) 85 | 86 | 87 | 88 | 89 | .. image:: windfarm_files/windfarm_5_1.png 90 | 91 | 92 | MPC 93 | --- 94 | 95 | Autoregressive model 96 | ~~~~~~~~~~~~~~~~~~~~ 97 | 98 | .. code:: python 99 | 100 | from sklearn import linear_model 101 | 102 | H = 4*6 103 | p_wind_residual = p_wind - p_wind_mean 104 | X = np.hstack([p_wind_residual.shift(x).fillna(0).reshape(-1,1) for x in xrange(1,H+1)]) 105 | lr = linear_model.RidgeCV() 106 | lr.fit(X, p_wind_residual) 107 | 108 | def predict_wind(t, T): 109 | r = np.zeros(T) 110 | x = X[t,:] 111 | for i in xrange(T): 112 | tau = t+i 113 | r[i] = lr.predict(x.reshape(1,-1)) 114 | x = np.hstack((r[i], x[:-1])) 115 | return np.maximum(p_wind_mean[t:t+T] + r, 0) 116 | 117 | t = 4*24*2 118 | T = 4*24 119 | compare = pd.DataFrame(index=p_wind.index) 120 | compare["p_wind"] = p_wind 121 | compare["p_wind_pred"] = pd.Series(predict_wind(t, T), index=p_wind.index[t:t+T]) 122 | compare["p_out"] = p_wind_mean 123 | compare["2011-01-02":"2011-01-03"].plot() 124 | 125 | 126 | 127 | 128 | .. parsed-literal:: 129 | 130 | 131 | 132 | 133 | 134 | 135 | .. image:: windfarm_files/windfarm_8_1.png 136 | 137 | 138 | .. code:: python 139 | 140 | T = 4*24 141 | out = FixedLoad(power=Parameter(T+1), name="Output") 142 | wind_gen = Generator(alpha=0, beta=0, power_min=0, power_max=Parameter(T+1), name="Wind") 143 | gas_gen = Generator(alpha=0.02, beta=1, power_min=0.01, power_max=1, name="Gas") 144 | storage = Storage(discharge_max=1, charge_max=1, energy_max=12*4, energy_init=Parameter(1, value=6*4)) 145 | net = Net([wind_gen.terminals[0], 146 | gas_gen.terminals[0], 147 | storage.terminals[0], 148 | out.terminals[0]]) 149 | network = Group([wind_gen, gas_gen, storage, out], [net]) 150 | network.init_problem(time_horizon=T+1) 151 | 152 | def predict(t): 153 | out.power.value = p_wind_mean[t:t+T+1].as_matrix()/16 154 | wind_gen.power_max.value = np.hstack((p_wind[t], predict_wind(t+1,T)))/16 155 | 156 | def execute(t): 157 | energy_stored[t] = storage.energy.value[0] 158 | storage.energy_init.value = storage.energy.value[0] 159 | 160 | N = 4*24*7 161 | energy_stored = np.empty(N) 162 | results = run_mpc(network, N, predict, execute) 163 | 164 | 165 | .. parsed-literal:: 166 | 167 | 100%|██████████| 672/672 [00:29<00:00, 22.65it/s] 168 | 169 | 170 | .. code:: python 171 | 172 | # plot the results 173 | results.plot() 174 | 175 | # plot energy stored in battery 176 | fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(16,3)) 177 | ax.plot(energy_stored) 178 | ax.set_ylabel("energy stored") 179 | 180 | 181 | 182 | 183 | .. parsed-literal:: 184 | 185 | 186 | 187 | 188 | 189 | 190 | .. image:: windfarm_files/windfarm_10_1.png 191 | 192 | 193 | 194 | .. image:: windfarm_files/windfarm_10_2.png 195 | 196 | 197 | Wind curtailment 198 | ~~~~~~~~~~~~~~~~ 199 | 200 | Current model is not too smart about using all the available wind power, 201 | at times it doesn't even charge the battery when there is extra power 202 | available... 203 | 204 | .. code:: python 205 | 206 | plt.plot(xrange(N), p_wind[:N]/16, label="p_wind_max") 207 | plt.plot(-results.power[(wind_gen, 0)], label="p_wind") 208 | plt.legend() 209 | 210 | 211 | 212 | 213 | .. parsed-literal:: 214 | 215 | 216 | 217 | 218 | 219 | 220 | .. image:: windfarm_files/windfarm_12_1.png 221 | 222 | 223 | Robust MPC 224 | ---------- 225 | 226 | Probabilistic predictions 227 | ~~~~~~~~~~~~~~~~~~~~~~~~~ 228 | 229 | .. code:: python 230 | 231 | from sklearn import linear_model 232 | 233 | H = 4*6 234 | p_wind_residual = p_wind - p_wind_mean 235 | X = np.hstack([p_wind_residual.shift(x).fillna(0).reshape(-1,1) for x in xrange(1,H+1)]) 236 | lr = linear_model.RidgeCV() 237 | lr.fit(X, p_wind_residual) 238 | sigma = np.std(lr.predict(X) - p_wind_residual) 239 | 240 | def predict_wind_probs(t, T, K): 241 | np.random.seed(0) 242 | R = np.empty((T,K)) 243 | Xp = np.tile(X[t,:], (K,1)) # K x H prediction matrix 244 | for i in xrange(T): 245 | tau = t+i 246 | R[i,:] = lr.predict(Xp) + sigma*np.random.randn(K) 247 | Xp = np.hstack((R[i:i+1,:].T, Xp[:,:-1])) 248 | return np.minimum(np.maximum(np.tile(p_wind_mean[t:t+T], (K,1)).T + R, 0), 16) 249 | 250 | # plot an example of predictions at a particular time 251 | idx = slice("2011-01-02", "2011-01-03") 252 | t = 4*24*2 253 | T = 4*24 254 | K = 10 255 | wind_probs = predict_wind_probs(t, T, K) 256 | ax0 = plt.plot(p_wind[idx].index, p_wind[idx]) 257 | ax1 = plt.plot(p_wind.index[t:t+T], wind_probs, color="b", alpha=0.3) 258 | ax2 = plt.plot(p_wind_mean[idx].index, p_wind_mean[idx]) 259 | plt.legend(handles=[ax0[0], ax1[0], ax2[0]], labels=["p_wind", "p_wind_pred", "p_out"]) 260 | 261 | 262 | 263 | 264 | .. parsed-literal:: 265 | 266 | 267 | 268 | 269 | 270 | 271 | .. image:: windfarm_files/windfarm_15_1.png 272 | 273 | 274 | .. code:: python 275 | 276 | T = 24*4*2 # 48 hours, 15 minute intervals 277 | K = 10 # 10 scenarios 278 | 279 | out = FixedLoad(power=Parameter(T+1,K), name="Output") 280 | wind_gen = Generator(alpha=0, beta=0, power_min=0, power_max=Parameter(T+1,K), name="Wind") 281 | gas_gen = Generator(alpha=0.02, beta=1, power_min=0.01, power_max=1, name="Gas") 282 | storage = Storage(discharge_max=1, charge_max=1, energy_max=12*4, energy_init=Parameter(1, value=6*4)) 283 | net = Net([wind_gen.terminals[0], 284 | gas_gen.terminals[0], 285 | storage.terminals[0], 286 | out.terminals[0]]) 287 | network = Group([wind_gen, gas_gen, storage, out], [net]) 288 | network.init_problem(time_horizon=T+1, num_scenarios=K) 289 | 290 | def predict(t): 291 | out.power.value = np.tile(p_wind_mean[t:t+T+1], (K,1)).T / 16 292 | wind_gen.power_max.value = np.empty((T+1, K)) 293 | wind_gen.power_max.value[0,:] = p_wind[t] / 16 294 | wind_gen.power_max.value[1:,:] = predict_wind_probs(t+1,T,K) / 16 295 | 296 | def execute(t): 297 | energy_stored[t] = storage.energy.value[0,0] 298 | storage.energy_init.value = energy_stored[t] 299 | 300 | N = 7*24*4 # 7 days 301 | energy_stored = np.empty(N) 302 | results = run_mpc(network, N, predict, execute, solver=cvx.MOSEK) 303 | 304 | 305 | .. parsed-literal:: 306 | 307 | 100%|██████████| 672/672 [1:21:08<00:00, 5.88s/it] 308 | 309 | 310 | .. code:: python 311 | 312 | # plot the results 313 | results.plot() 314 | 315 | # plot energy stored in battery 316 | fig, ax = plt.subplots(nrows=1, ncols=1, figsize=(16,3)) 317 | ax.plot(energy_stored) 318 | ax.set_ylabel("energy stored") 319 | 320 | 321 | 322 | 323 | .. parsed-literal:: 324 | 325 | 326 | 327 | 328 | 329 | 330 | .. image:: windfarm_files/windfarm_17_1.png 331 | 332 | 333 | 334 | .. image:: windfarm_files/windfarm_17_2.png 335 | 336 | 337 | .. code:: python 338 | 339 | plt.plot(xrange(N), p_wind[:N]/16, label="p_wind_max") 340 | plt.plot(-results.power[(wind_gen, 0)], label="p_wind") 341 | plt.legend() 342 | 343 | 344 | 345 | 346 | .. parsed-literal:: 347 | 348 | 349 | 350 | 351 | 352 | 353 | .. image:: windfarm_files/windfarm_18_1.png 354 | 355 | -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_10_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_10_1.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_10_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_10_2.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_11_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_11_1.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_11_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_11_2.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_12_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_12_1.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_13_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_13_1.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_15_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_15_1.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_17_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_17_1.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_17_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_17_2.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_18_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_18_1.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_3_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_3_1.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_5_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_5_1.png -------------------------------------------------------------------------------- /docs/notebooks/windfarm_files/windfarm_8_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/docs/notebooks/windfarm_files/windfarm_8_1.png -------------------------------------------------------------------------------- /examples/HistoricalEMSHourlyLoad_2014-2016.xlsx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/examples/HistoricalEMSHourlyLoad_2014-2016.xlsx -------------------------------------------------------------------------------- /examples/ThreeBusExample.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 1, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "import os\n", 10 | "import sys\n", 11 | "sys.path.insert(0, os.path.abspath('..'))\n", 12 | "from cvxpower import *\n", 13 | "\n", 14 | "%matplotlib inline\n", 15 | "import matplotlib.pyplot as plt\n", 16 | "import numpy as np\n", 17 | "#from config import *" 18 | ] 19 | }, 20 | { 21 | "cell_type": "code", 22 | "execution_count": 2, 23 | "metadata": {}, 24 | "outputs": [ 25 | { 26 | "name": "stdout", 27 | "output_type": "stream", 28 | "text": [ 29 | "Status: optimal\n", 30 | "Terminal Power\n", 31 | "-------- -----\n", 32 | "load1[0] 50.00\n", 33 | "load2[0] 100.00\n", 34 | "gen1[0] -90.00\n", 35 | "gen2[0] -60.00\n", 36 | "line1[0] 50.00\n", 37 | "line1[1] -50.00\n", 38 | "line2[0] -10.00\n", 39 | "line2[1] 10.00\n", 40 | "line3[0] -50.00\n", 41 | "line3[1] 50.00\n", 42 | "\n", 43 | "Net Price\n", 44 | "--- -----\n", 45 | "net1 33.6000\n", 46 | "net2 199.5965\n", 47 | "net3 24.0012\n", 48 | "\n", 49 | "Device Payment\n", 50 | "------ -------\n", 51 | "load1 1680.00\n", 52 | "load2 19959.65\n", 53 | "gen1 -3024.00\n", 54 | "gen2 -1440.07\n", 55 | "line1 -8299.83\n", 56 | "line2 -95.99\n", 57 | "line3 -8779.76\n", 58 | "\n", 59 | "Power and price are averages over the time horizon. Payment is total.\n", 60 | "\n" 61 | ] 62 | } 63 | ], 64 | "source": [ 65 | "load1 = FixedLoad(power=50, name=\"load1\")\n", 66 | "load2 = FixedLoad(power=100, name=\"load2\")\n", 67 | "\n", 68 | "gen1 = Generator(power_max=1000, alpha=0.02, beta=30, name=\"gen1\")\n", 69 | "gen2 = Generator(power_max=100, alpha=0.2, beta=0, name=\"gen2\")\n", 70 | "\n", 71 | "line1 = TransmissionLine(power_max=50, name='line1')\n", 72 | "line2 = TransmissionLine(power_max=10, name='line2')\n", 73 | "line3 = TransmissionLine(power_max=50, name='line3')\n", 74 | "\n", 75 | "net1 = Net([load1.terminals[0], gen1.terminals[0], \n", 76 | " line1.terminals[0], line2.terminals[0]], name = 'net1')\n", 77 | "net2 = Net([load2.terminals[0], line1.terminals[1], \n", 78 | " line3.terminals[0]], name = 'net2')\n", 79 | "net3 = Net([gen2.terminals[0], line2.terminals[1], \n", 80 | " line3.terminals[1]], name = 'net3')\n", 81 | "network = Group([load1, load2, gen1, gen2,\n", 82 | " line1, line2, line3],\n", 83 | " [net1, net2, net3])\n", 84 | "\n", 85 | "network.init_problem()\n", 86 | "network.optimize(solver='ECOS')\n", 87 | "print(network.results.summary())" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 5, 93 | "metadata": {}, 94 | "outputs": [ 95 | { 96 | "data": { 97 | "text/plain": [ 98 | "['__add__',\n", 99 | " '__class__',\n", 100 | " '__delattr__',\n", 101 | " '__dict__',\n", 102 | " '__dir__',\n", 103 | " '__doc__',\n", 104 | " '__eq__',\n", 105 | " '__format__',\n", 106 | " '__ge__',\n", 107 | " '__getattribute__',\n", 108 | " '__gt__',\n", 109 | " '__hash__',\n", 110 | " '__init__',\n", 111 | " '__init_subclass__',\n", 112 | " '__le__',\n", 113 | " '__lt__',\n", 114 | " '__module__',\n", 115 | " '__ne__',\n", 116 | " '__new__',\n", 117 | " '__radd__',\n", 118 | " '__reduce__',\n", 119 | " '__reduce_ex__',\n", 120 | " '__repr__',\n", 121 | " '__setattr__',\n", 122 | " '__sizeof__',\n", 123 | " '__str__',\n", 124 | " '__subclasshook__',\n", 125 | " '__weakref__',\n", 126 | " 'payments',\n", 127 | " 'plot',\n", 128 | " 'power',\n", 129 | " 'price',\n", 130 | " 'status',\n", 131 | " 'summary']" 132 | ] 133 | }, 134 | "execution_count": 5, 135 | "metadata": {}, 136 | "output_type": "execute_result" 137 | } 138 | ], 139 | "source": [ 140 | "flows = pd.Series()\n", 141 | "dir(network.results)" 142 | ] 143 | }, 144 | { 145 | "cell_type": "code", 146 | "execution_count": 64, 147 | "metadata": {}, 148 | "outputs": [ 149 | { 150 | "data": { 151 | "text/plain": [ 152 | "{: array([[24.00116919]]),\n", 153 | " : array([[33.59995155]]),\n", 154 | " : array([[199.59646122]])}" 155 | ] 156 | }, 157 | "execution_count": 64, 158 | "metadata": {}, 159 | "output_type": "execute_result" 160 | } 161 | ], 162 | "source": [ 163 | "network.results.price" 164 | ] 165 | }, 166 | { 167 | "cell_type": "code", 168 | "execution_count": 50, 169 | "metadata": {}, 170 | "outputs": [ 171 | { 172 | "name": "stdout", 173 | "output_type": "stream", 174 | "text": [ 175 | "\\begin{tabular}{lll}\n", 176 | "\\toprule\n", 177 | "{} & terminal 1 & terminal 2 \\\\\n", 178 | "\\midrule\n", 179 | "load1 & 50MW & - \\\\\n", 180 | "load2 & 100MW & - \\\\\n", 181 | "gen1 & -90MW & - \\\\\n", 182 | "gen2 & -60MW & - \\\\\n", 183 | "line1 & 50MW & -50MW \\\\\n", 184 | "line2 & -10MW & 10MW \\\\\n", 185 | "line3 & -50MW & 50MW \\\\\n", 186 | "\\bottomrule\n", 187 | "\\end{tabular}\n", 188 | "\n" 189 | ] 190 | } 191 | ], 192 | "source": [ 193 | "import pandas as pd\n", 194 | "flows = pd.DataFrame(columns=['terminal 1', 'terminal 2'])\n", 195 | "for (device, terminal) in network.results.power.keys():\n", 196 | " flows.loc[device.name, 'terminal %d' % (terminal +1)] = network.results.power[(device, terminal)][0][0]\n", 197 | " \n", 198 | "print(flows.to_latex(na_rep = '-', float_format=lambda x: '%.0fMW' % x))" 199 | ] 200 | }, 201 | { 202 | "cell_type": "code", 203 | "execution_count": null, 204 | "metadata": {}, 205 | "outputs": [], 206 | "source": [ 207 | "import pandas as pd\n", 208 | "prices = pd.DataFrame(columns=['price'])\n", 209 | "for (device, terminal) in network.results.power.keys():\n", 210 | " flows.loc[device.name, 'terminal %d' % (terminal +1)] = network.results.power[(device, terminal)][0][0]\n", 211 | " \n", 212 | "print(flows.to_latex(na_rep = '-', float_format=lambda x: '%.0fMW' % x))" 213 | ] 214 | }, 215 | { 216 | "cell_type": "code", 217 | "execution_count": 63, 218 | "metadata": {}, 219 | "outputs": [ 220 | { 221 | "name": "stdout", 222 | "output_type": "stream", 223 | "text": [ 224 | "\\begin{tabular}{lllr}\n", 225 | "\\toprule\n", 226 | "{} & terminal 1 & terminal 2 & total \\\\\n", 227 | "\\midrule\n", 228 | "load1 & \\$1680 & - & \\$1680 \\\\\n", 229 | "load2 & \\$19960 & - & \\$19960 \\\\\n", 230 | "gen1 & -\\$3024 & - & -\\$3024 \\\\\n", 231 | "gen2 & -\\$1440 & - & -\\$1440 \\\\\n", 232 | "line1 & \\$1680 & -\\$9980 & -\\$8300 \\\\\n", 233 | "line2 & -\\$336 & \\$240 & -\\$96 \\\\\n", 234 | "line3 & -\\$9980 & \\$1200 & -\\$8780 \\\\\n", 235 | "\\bottomrule\n", 236 | "\\end{tabular}\n", 237 | "\n" 238 | ] 239 | } 240 | ], 241 | "source": [ 242 | "payments = pd.DataFrame(columns=['terminal 1', 'terminal 2', 'total'])\n", 243 | "for (device, terminal) in network.results.payments.keys():\n", 244 | " payments.loc[device.name, 'terminal %d' % (terminal +1)] = network.results.payments[(device, terminal)][0][0]\n", 245 | "\n", 246 | "payments['total'] = payments.iloc[:,:2].sum(1)\n", 247 | "print(payments.to_latex(na_rep = '-', float_format=lambda x: ('-' if np.sign(x) < 0 else '') + '$%.0f' % np.abs(x)))" 248 | ] 249 | }, 250 | { 251 | "cell_type": "code", 252 | "execution_count": 3, 253 | "metadata": {}, 254 | "outputs": [ 255 | { 256 | "data": { 257 | "image/png": "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\n", 258 | "text/plain": [ 259 | "" 260 | ] 261 | }, 262 | "metadata": {}, 263 | "output_type": "display_data" 264 | } 265 | ], 266 | "source": [ 267 | "alpha1, beta1, pmax1 = 0.02, 30, 1000\n", 268 | "alpha2, beta2, pmax2 = 0.2, 0, 100\n", 269 | "\n", 270 | "#plt.figure(figsize=FIGREGULAR)\n", 271 | "xs = np.linspace(0.001,pmax1)\n", 272 | "plt.plot(xs, (alpha1*xs**2 + beta1*xs)/xs, label=r'$f_{\\rm gen1}$')\n", 273 | "xs = np.linspace(0.001,pmax2)\n", 274 | "plt.plot(xs, (alpha2*xs**2 + beta2*xs)/xs, label=r'$f_{\\rm gen2}$')\n", 275 | "plt.legend()\n", 276 | "plt.xlabel(r'power produced (MW)')\n", 277 | "plt.ylabel(r'Operating cost ($ / MWh)')\n", 278 | "#plt.xlim([0, 1000])\n", 279 | "#plt.ylim([0, max(pmax1, pmax2)])\n", 280 | "plt.savefig(\"three_bus_gen_cost.pdf\")" 281 | ] 282 | }, 283 | { 284 | "cell_type": "code", 285 | "execution_count": 24, 286 | "metadata": {}, 287 | "outputs": [], 288 | "source": [ 289 | "# load1 = FixedLoad(power=50, name=\"load1\")\n", 290 | "# load2 = FixedLoad(power=100, name=\"load2\")\n", 291 | "\n", 292 | "# gen1 = Generator(power_max=1000,\n", 293 | "# alpha=0.01, beta=100, name=\"gen1\")\n", 294 | "# gen2 = Generator(power_max=100, alpha=0.1,\n", 295 | "# beta=0.1, name=\"gen2\")\n", 296 | "\n", 297 | "# line1 = TransmissionLine(power_max=50, name = 'line1', alpha=0.001)\n", 298 | "# #line1 = TransmissionLine(name = 'line1', alpha=0.001)\n", 299 | "# line2 = TransmissionLine(power_max=10, name = 'line2')\n", 300 | "# line3 = TransmissionLine(power_max=50, name = 'line3')\n", 301 | "\n", 302 | "# net1 = Net([load1.terminals[0],\n", 303 | "# gen1.terminals[0], line1.terminals[0],\n", 304 | "# line2.terminals[0]], name = 'net1')\n", 305 | "# net2 = Net([load2.terminals[0], line1.terminals[1], \n", 306 | "# line3.terminals[0]], name = 'net2')\n", 307 | "# net3 = Net([gen2.terminals[0], line2.terminals[1], \n", 308 | "# line3.terminals[1]], name = 'net3')\n", 309 | "# network = Group([load1, load2, gen1, gen2,\n", 310 | "# line1, line2, line3],\n", 311 | "# [net1, net2, net3])\n", 312 | "\n", 313 | "# network.init_problem()\n", 314 | "# network.optimize()\n", 315 | "# print(network.results.summary())" 316 | ] 317 | }, 318 | { 319 | "cell_type": "code", 320 | "execution_count": null, 321 | "metadata": {}, 322 | "outputs": [], 323 | "source": [] 324 | } 325 | ], 326 | "metadata": { 327 | "kernelspec": { 328 | "display_name": "Python 3", 329 | "language": "python", 330 | "name": "python3" 331 | }, 332 | "language_info": { 333 | "codemirror_mode": { 334 | "name": "ipython", 335 | "version": 3 336 | }, 337 | "file_extension": ".py", 338 | "mimetype": "text/x-python", 339 | "name": "python", 340 | "nbconvert_exporter": "python", 341 | "pygments_lexer": "ipython3", 342 | "version": "3.6.4" 343 | } 344 | }, 345 | "nbformat": 4, 346 | "nbformat_minor": 2 347 | } 348 | -------------------------------------------------------------------------------- /examples/gen_bid_curve.py: -------------------------------------------------------------------------------- 1 | """An example of generator with cost function specified by bid curve.""" 2 | 3 | import cvxpy as cvx 4 | from dem import * 5 | 6 | 7 | class GeneratorWithBidCurve(Generator): 8 | """Generators with cost function specified by bid curve. 9 | 10 | Cost function will have the form: 11 | 12 | max(cost0, a_1*p + b_1, ..., a_N*p + b_N), 13 | 14 | where a_i is the price for bid curve segment i and b_i is the appropriate 15 | offset, depending on previous segments. 16 | """ 17 | 18 | def __init__(self, no_load_cost=0, bid_curve=[], name=None): 19 | super(GeneratorWithBidCurve, self).__init__(name=name) 20 | self.no_load_cost = no_load_cost 21 | self.bid_curve = bid_curve 22 | 23 | @property 24 | def cost(self): 25 | p = -self.terminals[0].power_var 26 | 27 | segments = [cvx.Constant(self.no_load_cost)] 28 | prev_power = None 29 | for power, price in self.bid_curve[1:]: 30 | if prev_power is None: 31 | offset = self.no_load_cost 32 | else: 33 | offset += (power - prev_power) * prev_price 34 | segments.append(price * (p - power) + offset) 35 | prev_power = power 36 | prev_price = price 37 | 38 | return cvx.max_elemwise(*segments) 39 | 40 | 41 | gen = GeneratorWithBidCurve( 42 | no_load_cost=290.42, 43 | bid_curve=[ 44 | (30, 21.21), 45 | (33.1, 21.43), 46 | (36.2, 21.66), 47 | (39.4, 21.93), 48 | (42.5, 22.22), 49 | (45.6, 22.53), 50 | (48.8, 22.87), 51 | (51.9, 23.22), 52 | (55, 23.6), 53 | ], 54 | ) 55 | load = FixedLoad(power=43) 56 | net = Net([gen.terminals[0], load.terminals[0]]) 57 | 58 | network = Group([gen, load], [net]) 59 | print(network.optimize()) 60 | -------------------------------------------------------------------------------- /examples/ieee14cdf.txt: -------------------------------------------------------------------------------- 1 | 08/19/93 UW ARCHIVE 100.0 1962 W IEEE 14 Bus Test Case 2 | BUS DATA FOLLOWS 14 ITEMS 3 | 1 Bus 1 HV 1 1 3 1.060 0.0 0.0 0.0 232.4 -16.9 0.0 1.060 0.0 0.0 0.0 0.0 0 4 | 2 Bus 2 HV 1 1 2 1.045 -4.98 21.7 12.7 40.0 42.4 0.0 1.045 50.0 -40.0 0.0 0.0 0 5 | 3 Bus 3 HV 1 1 2 1.010 -12.72 94.2 19.0 0.0 23.4 0.0 1.010 40.0 0.0 0.0 0.0 0 6 | 4 Bus 4 HV 1 1 0 1.019 -10.33 47.8 -3.9 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 7 | 5 Bus 5 HV 1 1 0 1.020 -8.78 7.6 1.6 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 8 | 6 Bus 6 LV 1 1 2 1.070 -14.22 11.2 7.5 0.0 12.2 0.0 1.070 24.0 -6.0 0.0 0.0 0 9 | 7 Bus 7 ZV 1 1 0 1.062 -13.37 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 10 | 8 Bus 8 TV 1 1 2 1.090 -13.36 0.0 0.0 0.0 17.4 0.0 1.090 24.0 -6.0 0.0 0.0 0 11 | 9 Bus 9 LV 1 1 0 1.056 -14.94 29.5 16.6 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.19 0 12 | 10 Bus 10 LV 1 1 0 1.051 -15.10 9.0 5.8 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 13 | 11 Bus 11 LV 1 1 0 1.057 -14.79 3.5 1.8 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 14 | 12 Bus 12 LV 1 1 0 1.055 -15.07 6.1 1.6 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 15 | 13 Bus 13 LV 1 1 0 1.050 -15.16 13.5 5.8 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 16 | 14 Bus 14 LV 1 1 0 1.036 -16.04 14.9 5.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 0 17 | -999 18 | BRANCH DATA FOLLOWS 20 ITEMS 19 | 1 2 1 1 1 0 0.01938 0.05917 0.0528 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 20 | 1 5 1 1 1 0 0.05403 0.22304 0.0492 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 21 | 2 3 1 1 1 0 0.04699 0.19797 0.0438 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 22 | 2 4 1 1 1 0 0.05811 0.17632 0.0340 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 23 | 2 5 1 1 1 0 0.05695 0.17388 0.0346 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 24 | 3 4 1 1 1 0 0.06701 0.17103 0.0128 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 25 | 4 5 1 1 1 0 0.01335 0.04211 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 26 | 4 7 1 1 1 0 0.0 0.20912 0.0 0 0 0 0 0 0.978 0.0 0.0 0.0 0.0 0.0 0.0 27 | 4 9 1 1 1 0 0.0 0.55618 0.0 0 0 0 0 0 0.969 0.0 0.0 0.0 0.0 0.0 0.0 28 | 5 6 1 1 1 0 0.0 0.25202 0.0 0 0 0 0 0 0.932 0.0 0.0 0.0 0.0 0.0 0.0 29 | 6 11 1 1 1 0 0.09498 0.19890 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 30 | 6 12 1 1 1 0 0.12291 0.25581 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 31 | 6 13 1 1 1 0 0.06615 0.13027 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 32 | 7 8 1 1 1 0 0.0 0.17615 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 33 | 7 9 1 1 1 0 0.0 0.11001 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 34 | 9 10 1 1 1 0 0.03181 0.08450 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 35 | 9 14 1 1 1 0 0.12711 0.27038 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 36 | 10 11 1 1 1 0 0.08205 0.19207 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 37 | 12 13 1 1 1 0 0.22092 0.19988 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 38 | 13 14 1 1 1 0 0.17093 0.34802 0.0 0 0 0 0 0 0.0 0.0 0.0 0.0 0.0 0.0 0.0 39 | -999 40 | LOSS ZONES FOLLOWS 1 ITEMS 41 | 1 IEEE 14 BUS 42 | -99 43 | INTERCHANGE DATA FOLLOWS 1 ITEMS 44 | 1 2 Bus 2 HV 0.0 999.99 IEEE14 IEEE 14 Bus Test Case 45 | -9 46 | TIE LINES FOLLOWS 0 ITEMS 47 | -999 48 | END OF DATA 49 | -------------------------------------------------------------------------------- /examples/p_wind.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/examples/p_wind.pickle -------------------------------------------------------------------------------- /examples/residual_params.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/examples/residual_params.pickle -------------------------------------------------------------------------------- /examples/sigma_epsilon.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/examples/sigma_epsilon.pickle -------------------------------------------------------------------------------- /examples/static.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "markdown", 5 | "metadata": {}, 6 | "source": [ 7 | "# Static power flow" 8 | ] 9 | }, 10 | { 11 | "cell_type": "code", 12 | "execution_count": 8, 13 | "metadata": {}, 14 | "outputs": [], 15 | "source": [ 16 | "%matplotlib inline\n", 17 | "import matplotlib.pyplot as plt\n", 18 | "import matplotlib\n", 19 | "import numpy as np\n", 20 | "\n", 21 | "from cvxpower import *\n", 22 | "\n", 23 | "matplotlib.rc(\"figure\", figsize=(7,7))\n", 24 | "matplotlib.rc(\"lines\", linewidth=2)" 25 | ] 26 | }, 27 | { 28 | "cell_type": "markdown", 29 | "metadata": {}, 30 | "source": [ 31 | "## Basic examples" 32 | ] 33 | }, 34 | { 35 | "cell_type": "markdown", 36 | "metadata": {}, 37 | "source": [ 38 | "### Hello world" 39 | ] 40 | }, 41 | { 42 | "cell_type": "code", 43 | "execution_count": 2, 44 | "metadata": {}, 45 | "outputs": [ 46 | { 47 | "name": "stdout", 48 | "output_type": "stream", 49 | "text": [ 50 | "Status: optimal\n", 51 | "Terminal Power\n", 52 | "-------- -----\n", 53 | "FixedLoad[0] 100.00\n", 54 | "Generator[0] -100.00\n", 55 | "\n", 56 | "Net Price\n", 57 | "--- -----\n", 58 | "Net 102.0000\n", 59 | "\n", 60 | "Device Payment\n", 61 | "------ -------\n", 62 | "FixedLoad 10200.00\n", 63 | "Generator -10200.00\n", 64 | "\n", 65 | "Power and price are averages over the time horizon. Payment is total.\n", 66 | "\n" 67 | ] 68 | } 69 | ], 70 | "source": [ 71 | "load = FixedLoad(power=100)\n", 72 | "gen = Generator(power_max=1000, alpha=0.01, beta=100)\n", 73 | "net = Net([load.terminals[0], gen.terminals[0]])\n", 74 | "network = Group([load, gen], [net])\n", 75 | "\n", 76 | "network.init_problem()\n", 77 | "network.problem.solve()\n", 78 | "print(network.results.summary())" 79 | ] 80 | }, 81 | { 82 | "cell_type": "markdown", 83 | "metadata": {}, 84 | "source": [ 85 | "### Curtailable load" 86 | ] 87 | }, 88 | { 89 | "cell_type": "code", 90 | "execution_count": 3, 91 | "metadata": {}, 92 | "outputs": [ 93 | { 94 | "name": "stdout", 95 | "output_type": "stream", 96 | "text": [ 97 | "Status: optimal\n", 98 | "Terminal Power\n", 99 | "-------- -----\n", 100 | "CurtailableLoad[0] 25.00\n", 101 | "Generator[0] -25.00\n", 102 | "\n", 103 | "Net Price\n", 104 | "--- -----\n", 105 | "Net 150.0000\n", 106 | "\n", 107 | "Device Payment\n", 108 | "------ -------\n", 109 | "CurtailableLoad 3750.00\n", 110 | "Generator -3750.00\n", 111 | "\n", 112 | "Power and price are averages over the time horizon. Payment is total.\n", 113 | "\n" 114 | ] 115 | } 116 | ], 117 | "source": [ 118 | "load = CurtailableLoad(power=1000, alpha=150)\n", 119 | "gen = Generator(power_max=1000, alpha=1, beta=100)\n", 120 | "net = Net([load.terminals[0], gen.terminals[0]])\n", 121 | "network = Group([load, gen], [net])\n", 122 | "\n", 123 | "network.init_problem()\n", 124 | "network.problem.solve()\n", 125 | "print(network.results.summary())" 126 | ] 127 | }, 128 | { 129 | "cell_type": "markdown", 130 | "metadata": {}, 131 | "source": [ 132 | "### Two generators, transmission line" 133 | ] 134 | }, 135 | { 136 | "cell_type": "code", 137 | "execution_count": 4, 138 | "metadata": {}, 139 | "outputs": [ 140 | { 141 | "name": "stdout", 142 | "output_type": "stream", 143 | "text": [ 144 | "Status: optimal\n", 145 | "Terminal Power\n", 146 | "-------- -----\n", 147 | "FixedLoad[0] 100.00\n", 148 | "Gen1[0] -50.00\n", 149 | "Gen2[0] -50.00\n", 150 | "TransmissionLine[0] -50.00\n", 151 | "TransmissionLine[1] 50.00\n", 152 | "\n", 153 | "Net Price\n", 154 | "--- -----\n", 155 | "Net 101.0000\n", 156 | "Net 10.1000\n", 157 | "\n", 158 | "Device Payment\n", 159 | "------ -------\n", 160 | "FixedLoad 10100.00\n", 161 | "Gen1 -5050.00\n", 162 | "Gen2 -505.00\n", 163 | "TransmissionLine -4545.00\n", 164 | "\n", 165 | "Power and price are averages over the time horizon. Payment is total.\n", 166 | "\n" 167 | ] 168 | } 169 | ], 170 | "source": [ 171 | "load = FixedLoad(power=100)\n", 172 | "gen1 = Generator(power_max=1000, alpha=0.01, beta=100, name=\"Gen1\")\n", 173 | "gen2 = Generator(power_max=100, alpha=0.1, beta=0.1, name=\"Gen2\")\n", 174 | "line = TransmissionLine(power_max=50)\n", 175 | "\n", 176 | "net1 = Net([load.terminals[0], gen1.terminals[0], line.terminals[0]])\n", 177 | "net2 = Net([gen2.terminals[0], line.terminals[1]])\n", 178 | "network = Group([load, gen1, gen2, line], [net1, net2])\n", 179 | "\n", 180 | "network.init_problem()\n", 181 | "network.problem.solve()\n", 182 | "print(network.results.summary())" 183 | ] 184 | }, 185 | { 186 | "cell_type": "markdown", 187 | "metadata": {}, 188 | "source": [ 189 | "### Three buses\n", 190 | "\n", 191 | "Figure 2.1 from Kraning, et al. without the battery.\n", 192 | "\n", 193 | "![Three bus example](./three_bus.png)\n", 194 | "\n" 195 | ] 196 | }, 197 | { 198 | "cell_type": "code", 199 | "execution_count": 5, 200 | "metadata": {}, 201 | "outputs": [ 202 | { 203 | "name": "stdout", 204 | "output_type": "stream", 205 | "text": [ 206 | "Status: optimal\n", 207 | "Terminal Power\n", 208 | "-------- -----\n", 209 | "Load1[0] 50.00\n", 210 | "Load2[0] 100.00\n", 211 | "Gen1[0] -90.00\n", 212 | "Gen2[0] -60.00\n", 213 | "TransmissionLine[0] 50.00\n", 214 | "TransmissionLine[1] -50.00\n", 215 | "TransmissionLine[0] -10.00\n", 216 | "TransmissionLine[1] 10.00\n", 217 | "TransmissionLine[0] -50.00\n", 218 | "TransmissionLine[1] 50.00\n", 219 | "\n", 220 | "Net Price\n", 221 | "--- -----\n", 222 | "Net 101.8001\n", 223 | "Net 926.6617\n", 224 | "Net 12.1000\n", 225 | "\n", 226 | "Device Payment\n", 227 | "------ -------\n", 228 | "Load1 5090.00\n", 229 | "Load2 92666.17\n", 230 | "Gen1 -9161.98\n", 231 | "Gen2 -726.00\n", 232 | "TransmissionLine -41243.11\n", 233 | "TransmissionLine -897.03\n", 234 | "TransmissionLine -45728.06\n", 235 | "\n", 236 | "Power and price are averages over the time horizon. Payment is total.\n", 237 | "\n" 238 | ] 239 | } 240 | ], 241 | "source": [ 242 | "load1 = FixedLoad(power=50, name=\"Load1\")\n", 243 | "load2 = FixedLoad(power=100, name=\"Load2\")\n", 244 | "gen1 = Generator(power_max=1000, alpha=0.01, beta=100, name=\"Gen1\")\n", 245 | "gen2 = Generator(power_max=100, alpha=0.1, beta=0.1, name=\"Gen2\")\n", 246 | "line1 = TransmissionLine(power_max=50)\n", 247 | "line2 = TransmissionLine(power_max=10)\n", 248 | "line3 = TransmissionLine(power_max=50)\n", 249 | "\n", 250 | "net1 = Net([load1.terminals[0], gen1.terminals[0], line1.terminals[0], line2.terminals[0]])\n", 251 | "net2 = Net([load2.terminals[0], line1.terminals[1], line3.terminals[0]])\n", 252 | "net3 = Net([gen2.terminals[0], line2.terminals[1], line3.terminals[1]])\n", 253 | "network = Group([load1, load2, gen1, gen2, line1, line2, line3], [net1, net2, net3])\n", 254 | "\n", 255 | "network.init_problem()\n", 256 | "network.problem.solve()\n", 257 | "print(network.results.summary())" 258 | ] 259 | }, 260 | { 261 | "cell_type": "markdown", 262 | "metadata": {}, 263 | "source": [ 264 | "## Grouping devices\n", 265 | "\n", 266 | "We can wrap up several devices and nets into a single device" 267 | ] 268 | }, 269 | { 270 | "cell_type": "code", 271 | "execution_count": 6, 272 | "metadata": {}, 273 | "outputs": [ 274 | { 275 | "name": "stdout", 276 | "output_type": "stream", 277 | "text": [ 278 | "Status: optimal\n", 279 | "Terminal Power\n", 280 | "-------- -----\n", 281 | "Solar[0] -10.00\n", 282 | "FixedLoad[0] 13.00\n", 283 | "TransmissionLine[0] -3.00\n", 284 | "TransmissionLine[1] 3.00\n", 285 | "Grid[0] -3.00\n", 286 | "\n", 287 | "Net Price\n", 288 | "--- -----\n", 289 | "Net 100.2993\n", 290 | "Meter 100.2998\n", 291 | "\n", 292 | "Device Payment\n", 293 | "------ -------\n", 294 | "Solar -1003.00\n", 295 | "FixedLoad 1303.89\n", 296 | "TransmissionLine 0.00\n", 297 | "Grid -300.90\n", 298 | "\n", 299 | "Power and price are averages over the time horizon. Payment is total.\n", 300 | "\n" 301 | ] 302 | } 303 | ], 304 | "source": [ 305 | "solar = Generator(power_max=10, alpha=0, beta=0, name=\"Solar\")\n", 306 | "load = FixedLoad(power=13)\n", 307 | "line = TransmissionLine(power_max=25)\n", 308 | "net = Net([load.terminals[0], solar.terminals[0], line.terminals[0]])\n", 309 | "home = Group([solar, load, line], [net], [line.terminals[1]], name=\"Home\")\n", 310 | "\n", 311 | "grid = Generator(power_max=1e6, alpha=0.05, beta=100, name=\"Grid\")\n", 312 | "meter = Net([line.terminals[1], grid.terminals[0]], name=\"Meter\")\n", 313 | "network = Group([home, grid], [meter])\n", 314 | "\n", 315 | "network.init_problem()\n", 316 | "network.problem.solve()\n", 317 | "print(network.results.summary())" 318 | ] 319 | }, 320 | { 321 | "cell_type": "markdown", 322 | "metadata": {}, 323 | "source": [ 324 | "## Varying parameters\n", 325 | "\n", 326 | "We can modify a single parameter and reoptimize, which is useful for sweeping over a parameter range." 327 | ] 328 | }, 329 | { 330 | "cell_type": "code", 331 | "execution_count": 7, 332 | "metadata": {}, 333 | "outputs": [ 334 | { 335 | "data": { 336 | "text/plain": [ 337 | "" 338 | ] 339 | }, 340 | "execution_count": 7, 341 | "metadata": {}, 342 | "output_type": "execute_result" 343 | }, 344 | { 345 | "data": { 346 | "image/png": "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\n", 347 | "text/plain": [ 348 | "
" 349 | ] 350 | }, 351 | "metadata": { 352 | "needs_background": "light" 353 | }, 354 | "output_type": "display_data" 355 | } 356 | ], 357 | "source": [ 358 | "load1 = FixedLoad(power=50, name=\"Load1\")\n", 359 | "load2 = FixedLoad(power=100, name=\"Load2\")\n", 360 | "gen1 = Generator(power_max=100, alpha=1, beta=10, name=\"Gen1\")\n", 361 | "gen2 = Generator(power_max=1000, alpha=0.01, beta=0, name=\"Gen2\")\n", 362 | "line1 = TransmissionLine(power_max=100)\n", 363 | "line2 = TransmissionLine(power_max=10)\n", 364 | "line3 = TransmissionLine(power_max=Parameter(1))\n", 365 | "\n", 366 | "net1 = Net([load1.terminals[0], gen1.terminals[0], line1.terminals[0], line2.terminals[0]])\n", 367 | "net2 = Net([load2.terminals[0], line1.terminals[1], line3.terminals[0]])\n", 368 | "net3 = Net([gen2.terminals[0], line2.terminals[1], line3.terminals[1]])\n", 369 | "network = Group([load1, load2, gen1, gen2, line1, line2, line3], [net1, net2, net3])\n", 370 | "\n", 371 | "network.init_problem()\n", 372 | "xs = np.linspace(0, 150, 100)\n", 373 | "prices = np.empty((len(xs), 3))\n", 374 | "for i, x in enumerate(xs):\n", 375 | " line3.power_max.value = [x]\n", 376 | " network.problem.solve()\n", 377 | " prices[i,:] = [net.price for net in network.nets]\n", 378 | " \n", 379 | "plt.plot(xs, prices)\n", 380 | "plt.xlabel(\"Line capacity\")\n", 381 | "plt.ylabel(\"Price\")\n", 382 | "plt.legend([\"Bus 1\", \"Bus 2\", \"Bus 3\"])" 383 | ] 384 | }, 385 | { 386 | "cell_type": "code", 387 | "execution_count": null, 388 | "metadata": {}, 389 | "outputs": [], 390 | "source": [] 391 | } 392 | ], 393 | "metadata": { 394 | "kernelspec": { 395 | "display_name": "Python 3", 396 | "language": "python", 397 | "name": "python3" 398 | }, 399 | "language_info": { 400 | "codemirror_mode": { 401 | "name": "ipython", 402 | "version": 3 403 | }, 404 | "file_extension": ".py", 405 | "mimetype": "text/x-python", 406 | "name": "python", 407 | "nbconvert_exporter": "python", 408 | "pygments_lexer": "ipython3", 409 | "version": "3.6.7" 410 | } 411 | }, 412 | "nbformat": 4, 413 | "nbformat_minor": 1 414 | } 415 | -------------------------------------------------------------------------------- /examples/three_bus.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/examples/three_bus.png -------------------------------------------------------------------------------- /examples/wind_baseline.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/examples/wind_baseline.pickle -------------------------------------------------------------------------------- /examples/wind_power_test.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/examples/wind_power_test.pickle -------------------------------------------------------------------------------- /examples/wind_power_train.pickle: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/cvxgrp/cvxpower/11c48a8d41eff897c05e716903f44908a77e6759/examples/wind_power_train.pickle -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup, find_packages 2 | 3 | setup( 4 | name="cvxpower", 5 | version="0.1.2", 6 | url="http://github.com/cvxgrp/cvxpower", 7 | packages=find_packages(), 8 | license="Apache", 9 | install_requires=["cvxpy>=1.0.6", "tqdm",], 10 | test_suite="cvxpower", 11 | description="Power Network Optimization and Simulation.", 12 | ) 13 | -------------------------------------------------------------------------------- /tools/generate_notebooks.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | notebooks=$PWD/notebooks/*.ipynb 4 | output=docs/notebooks 5 | 6 | cd $output 7 | for nb in $notebooks; do 8 | jupyter nbconvert $nb --to rst 9 | done 10 | -------------------------------------------------------------------------------- /tools/push_docker.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eu 2 | 3 | tag=cvxgrp/dem 4 | 5 | docker build -t $tag . 6 | docker push $tag 7 | -------------------------------------------------------------------------------- /tools/run_ci.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -eux 2 | # 3 | # Script for running continuous integration tests 4 | 5 | cd $(dirname "${BASH_SOURCE[0]}")/.. 6 | 7 | # Run Python 2.7 tests 8 | python2 setup.py test 9 | 10 | # Run Python 3.4 tests 11 | python3 setup.py test 12 | --------------------------------------------------------------------------------