├── Bisection.py ├── Consensus_Plus_Innovation_Final.py ├── Distributed_Proportional_Control.py └── README.md /Bisection.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Mar 8 19:38:16 2018 4 | 5 | @author: Andrew 6 | """ 7 | 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | import copy 11 | class GNode(): 12 | #class for a generator node 13 | def __init__(_self,a,b,ulim,llim): 14 | _self.a = a 15 | _self.b = b 16 | _self.ulim = ulim 17 | _self.llim = llim 18 | _self.umax = (_self.ulim - _self.a)/_self.b 19 | _self.umin = (_self.llim - _self.a)/_self.b 20 | _self.p = 0 21 | 22 | def u(_self, x): 23 | u = (x - _self.a)/_self.b 24 | u = np.clip(u,_self.umin,_self.umax) 25 | return(u) 26 | 27 | def solvex(_self,lamda): 28 | if lamda < _self.umin: 29 | return(_self.llim) 30 | if lamda >= _self.umax: 31 | return(_self.ulim) 32 | else: 33 | return(_self.a + lamda*_self.b) 34 | 35 | def cost(_self,x): 36 | cost = ((x - _self.a)**2)/(2*_self.b) 37 | return(cost) 38 | 39 | class SNode(): 40 | #simple placeholder for a node with static demand 41 | def __init__(_self,p): 42 | _self.p = p 43 | 44 | def consensus(array, W, maxitt = 10000, eps = .000001): 45 | #Runs an Eigenvector-consensus for a given list of nodes and a normalized adjacency matrix 46 | new = copy.copy(array) 47 | hist = [array[:]] 48 | itt = 0 49 | while itt < maxitt: 50 | old = hist[-1] 51 | for i in np.arange(0,len(array)): 52 | new[i] = np.dot(W[i,:],copy.copy(old)) 53 | itt += 1 54 | if np.all(np.abs(new - old) < eps): 55 | return([new,hist]) 56 | hist.append(copy.copy(new)) 57 | return([new,hist]) 58 | 59 | def maxconsensus(array,A): 60 | #runs a minimum consensus algorithm given a list of nodes and an Adjacency Matrix 61 | diameter = len(array) 62 | array = copy.copy(array) 63 | for i in np.arange(0,diameter): 64 | array[i] = np.max(A[i,:]*array) 65 | print(array) 66 | return(array[0]) 67 | 68 | def minconsensus(array,A): 69 | #runs a minimum consensus algorithm given a list of nodes and an Adjacency Matrix 70 | diameter = len(array) 71 | array = copy.copy(array) 72 | for i in np.arange(0,diameter): 73 | array[i] = np.min(A[i,:]*array) 74 | return(array[0]) 75 | 76 | def bisection_algorithm(eps = .0000001): 77 | #define the demand and the Normalized Adjacency Matrices 78 | p = np.array([0,0,0,.5,1.4,1.1,.9,.2]) 79 | ptot = np.sum(p) 80 | Q = np.array([1/3,1/5,0,1/4,0,0,0,0,1/3,1/5,1/5,0,0,1/5,0,1/3,0,1/5,1/5,0,0, 81 | 1/5,1/3,1/3,1/3,0,0,1/4,1/4,1/5,0,0,0,0,0,1/4,1/4,1/5,1/3,0,0, 82 | 1/5,1/5,1/4,1/4,1/5,0,0,0,0,1/5,0,1/4,0,1/3,0,0,1/5,1/5,0,0,0, 83 | 0,1/3]).reshape((8,8)) 84 | R = np.array([1/2,1/3,0,1/2,1/3,1/2,0,1/3,1/2]).reshape((3,3)) 85 | 86 | ##Initialize the Node Parameters for the Problem Graph 87 | n1 = GNode(-1,3,2.1,0) 88 | n2 = GNode(-1,2,1.0,0) 89 | n3 = GNode(-1,2,5.0,0) 90 | n4 = SNode(1) 91 | n5 = SNode(1.4) 92 | n6 = SNode(1.1) 93 | n7 = SNode(.9) 94 | n8 = SNode(.2) 95 | mvect = [n1,n2,n3,n4,n5,n6,n7,n8] 96 | nvects = [n1,n2,n3] 97 | lbounds = np.array([node.llim for node in nvects]) 98 | ubounds = np.array([node.ulim for node in nvects]) 99 | numnodes = len(mvect) 100 | numgens = len(nvects) 101 | diameter = 2 102 | 103 | 104 | [p,phist] = consensus(p,Q) 105 | 106 | sold = np.zeros((8,)) 107 | for i in np.arange(0,numgens): 108 | sold[i] = p[i] 109 | snew = sold 110 | [snew,hist] = consensus(snew,Q) 111 | 112 | ynew = p*p/snew 113 | [y,yhist] = consensus(ynew[0:3],R) 114 | 115 | lmin = np.array([node.umin for node in nvects]) 116 | lmax = np.array([node.umax for node in nvects]) 117 | AR = (R > 0) 118 | lmin = minconsensus(lmin,AR) 119 | lmax = maxconsensus(lmax,AR) 120 | lavg = (lmin + lmax)/2 121 | lhist = [lavg] 122 | x = [node.solvex(lavg) for node in nvects] 123 | xhist = [x] 124 | xsums = [np.sum(x)] 125 | zhist = [copy.copy(x)] 126 | signhist = [] 127 | costhist = [np.sum([mvect[i].cost(x[i]) for i in np.arange(numgens)])] 128 | for idx in np.arange(1,14): #bisection step 129 | z = copy.copy(xhist[-1]) 130 | done = False 131 | while not done: #iterate over z until convergence 132 | for count in np.arange(0,diameter): 133 | zold = copy.copy(z) 134 | for i in np.arange(0,numgens): 135 | z[i] = np.dot(R[i,:],zold[:]) 136 | sign = (z > y) 137 | zhist.append(z[:]) 138 | done = np.all(z>y - eps) or np.all(z<= y + eps) 139 | signhist.append(sign) 140 | if np.all(z>y): 141 | lmax = lavg 142 | else: 143 | lmin = lavg 144 | lavg = (lmin + lmax)/2 145 | lhist.append(lavg) 146 | x = [node.solvex(lavg) for node in nvects] 147 | xhist.append(x) 148 | xsums.append(np.sum(x)) 149 | costhist.append(np.sum([mvect[i].cost(x[i]) for i in np.arange(numgens)])) 150 | return((xhist,costhist,lhist)) 151 | 152 | (x,cost,l) = bisection_algorithm() -------------------------------------------------------------------------------- /Consensus_Plus_Innovation_Final.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Mar 8 19:38:16 2018 4 | 5 | @author: Andrew 6 | """ 7 | 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | import copy 11 | class GNode(): 12 | 13 | def __init__(_self,a,b,ulim,llim,pos): 14 | _self.a = a 15 | _self.b = b 16 | _self.ulim = ulim 17 | _self.llim = llim 18 | _self.umax = (_self.ulim - _self.a)/_self.b 19 | _self.umin = (_self.llim - _self.a)/_self.b 20 | _self.p = 0 21 | _self.lamda = (_self.umax + _self.umin)/2 22 | _self.x = (_self.llim + _self.ulim)/2 23 | _self.pos = pos 24 | 25 | def u(_self, x): #lamda at a given load 26 | u = (x - _self.a)/_self.b 27 | u = np.clip(u,_self.umin,_self.umax) 28 | return(u) 29 | 30 | def solvex(_self,lamda): 31 | if lamda < _self.umin: 32 | return(_self.llim) 33 | if lamda >= _self.umax: 34 | return(_self.ulim) 35 | else: 36 | return(_self.a + lamda*_self.b) 37 | 38 | def cost(_self,x): 39 | cost = ((x - _self.a)**2)/(2*_self.b) 40 | return(cost) 41 | 42 | class SNode(): 43 | 44 | def __init__(_self,p,pos): 45 | _self.p = p 46 | _self.lamda = 0 47 | _self.x = 0 48 | _self.pos = pos 49 | 50 | def solvex(_self, lamda): 51 | return(0) 52 | 53 | def consensus(array, W, maxitt = 10000, eps = .0000001): 54 | new = copy.copy(array) 55 | hist = [array[:]] 56 | itt = 0 57 | while itt < maxitt: 58 | old = hist[-1] 59 | for i in np.arange(0,len(array)): 60 | new[i] = np.dot(W[i,:],copy.copy(old)) 61 | itt += 1 62 | if np.all(np.abs(new - old) < eps): 63 | return([new,hist]) 64 | hist.append(copy.copy(new)) 65 | return([new,hist]) 66 | 67 | def maxconsensus(array,A): 68 | diameter = len(array) 69 | array = copy.copy(array) 70 | for i in np.arange(0,diameter): 71 | array[i] = np.max(A[i,:]*array) 72 | print(array) 73 | return(array[0]) 74 | 75 | def minconsensus(array,A): 76 | diameter = len(array) 77 | array = copy.copy(array) 78 | for i in np.arange(0,diameter): 79 | array[i] = np.min(A[i,:]*array) 80 | print(array) 81 | return(array[0]) 82 | 83 | def alpha(t,num = .5, power = .85): 84 | return(num/(t**power)) 85 | 86 | def beta(t,num = .2, power = .001): 87 | return(num/(t**power)) 88 | 89 | def lEqual(nodes, eps = .00001): 90 | flag = True 91 | for i in np.arange(0,len(nodes)-1): 92 | for j in np.arange(i+1,len(nodes)): 93 | if abs(nodes[i].lamda - nodes[j].lamda) > eps: 94 | flag = False 95 | return(flag) 96 | return(flag) 97 | 98 | def run(anum,apow,bnum,bpow): 99 | itt = 1 100 | maxitt = 200000 101 | eps = .00013 102 | lhist = [] 103 | xhist = [] 104 | x = [node.x for node in generators] 105 | costhist = [] 106 | while (abs(np.sum(x) - np.sum(p)) > eps and itt < maxitt) or itt == 1: 107 | betat = beta(itt,bnum,bpow) 108 | alphat = alpha(itt,anum,apow) 109 | betat = 1/180000 110 | alphat = 1/100000 111 | lams = [node.lamda for node in generators] 112 | cost = 0 113 | for node in generators: 114 | li = copy.copy(node.lamda) 115 | locallams = RQ[node.pos,:]*lams 116 | ldiffs = betat*np.sum([li - lj for lj in locallams]) 117 | xdiffs = alphat*(node.x - node.p) 118 | node.lamda = li - ldiffs - xdiffs 119 | node.x = node.solvex(node.lamda) 120 | cost += node.cost(node.x) 121 | itt += 1 122 | x = [node.x for node in generators] 123 | lams = [node.lamda for node in generators] 124 | xhist.append(x) 125 | lhist.append(lams) 126 | costhist.append(cost) 127 | lavg = np.mean(lams) 128 | xtot = np.sum(x) 129 | return((itt,x,lams,xhist,[np.sum(n) for n in xhist],costhist)) 130 | 131 | p = np.array([0,0,0,.5,1.4,1.1,.9,.2]) 132 | p_tot = np.sum(p) 133 | Q = np.array([1/3,1/5,0,1/4,0,0,0,0,1/3,1/5,1/5,0,0,1/5,0,1/3,0,1/5,1/5,0,0,1/5,1/3,1/3,1/3,0,0,1/4,1/4,1/5,0,0,0,0,0,1/4,1/4,1/5,1/3,0,0,1/5,1/5,1/4,1/4,1/5,0,0,0,0,1/5,0,1/4,0,1/3,0,0,1/5,1/5,0,0,0,0,1/3]) 134 | Q = Q.reshape((8,8)) 135 | AQ = Q > 0 136 | R = np.array([1/2,1/3,0,1/2,1/3,1/2,0,1/3,1/2]).reshape((3,3)) 137 | RQ = R > 0 138 | 139 | n1 = GNode(-1,3,2.1,0,0) 140 | n2 = GNode(-1,2,1.0,0,1) 141 | n3 = GNode(-1,2,5.0,0,2) 142 | n4 = SNode(1,3) 143 | n5 = SNode(1.4,4) 144 | n6 = SNode(1.1,5) 145 | n7 = SNode(.9,6) 146 | n8 = SNode(.2,7) 147 | nodes = [n1,n2,n3,n4,n5,n6,n7,n8] 148 | generators = [n3,n2,n1] 149 | lbounds = np.array([node.llim for node in generators]) 150 | ubounds = np.array([node.ulim for node in generators]) 151 | numnodes = len(nodes) 152 | numgens = len(generators) 153 | eps = .0001 154 | diameter = 2 155 | 156 | [p,phist] = consensus(p,Q) 157 | 158 | sold = np.zeros((8,)) 159 | for i in np.arange(0,numgens): 160 | sold[i] = p[i] 161 | snew = sold 162 | [snew,hist] = consensus(snew,Q) 163 | 164 | ynew = p*p/snew 165 | [y,yhist] = consensus(ynew[0:3],R) 166 | 167 | for i in np.arange(0,numgens): 168 | generators[i].p = y[i] 169 | 170 | (itt,x,lams,xhist,xdiffs,costhist) = run(.2,.01,.2,.00001) 171 | plt.plot(xhist) 172 | -------------------------------------------------------------------------------- /Distributed_Proportional_Control.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | """ 3 | Created on Thu Mar 8 19:38:16 2018 4 | 5 | @author: Andrew 6 | """ 7 | 8 | import numpy as np 9 | import matplotlib.pyplot as plt 10 | import copy 11 | class GNode(): 12 | 13 | def __init__(_self,a,b,ulim,llim,pos): 14 | _self.a = a 15 | _self.b = b 16 | _self.ulim = ulim 17 | _self.llim = llim 18 | _self.umax = (_self.ulim - _self.a)/_self.b 19 | _self.umin = (_self.llim - _self.a)/_self.b 20 | _self.p = 0 21 | _self.lamda = (_self.umax + _self.umin)/2 22 | _self.x = _self.solvex(_self.lamda) 23 | _self.pos = pos 24 | 25 | def u(_self, x): #lamda at a given load 26 | u = (x - _self.a)/_self.b 27 | u = np.clip(u,_self.umin,_self.umax) 28 | return(u) 29 | 30 | def solvex(_self,lamda): 31 | if lamda < _self.umin: 32 | return(_self.llim) 33 | if lamda >= _self.umax: 34 | return(_self.ulim) 35 | else: 36 | return(_self.a + lamda*_self.b) 37 | 38 | def cost(_self,x): 39 | cost = ((x - _self.a)**2)/(2*_self.b) 40 | return(cost) 41 | 42 | class SNode(): 43 | 44 | def __init__(_self,p,pos): 45 | _self.p = p 46 | _self.lamda = 0 47 | _self.x = 0 48 | _self.pos = pos 49 | 50 | def solvex(_self, lamda): 51 | return(0) 52 | 53 | def consensus(array, W, maxitt = 50000, eps = .0000001): 54 | new = copy.copy(array) 55 | hist = [array[:]] 56 | itt = 0 57 | while itt < maxitt: 58 | old = hist[-1] 59 | for i in np.arange(0,len(array)): 60 | new[i] = np.dot(W[i,:],copy.copy(old)) 61 | itt += 1 62 | if np.all(np.abs(new - old) < eps): 63 | return([new,hist]) 64 | hist.append(copy.copy(new)) 65 | return([new,hist]) 66 | 67 | def maxconsensus(array,A): 68 | diameter = len(array) 69 | array = copy.copy(array) 70 | for i in np.arange(0,diameter): 71 | array[i] = np.max(A[i,:]*array) 72 | print(array) 73 | return(array[0]) 74 | 75 | def minconsensus(array,A): 76 | diameter = len(array) 77 | array = copy.copy(array) 78 | for i in np.arange(0,diameter): 79 | array[i] = np.min(A[i,:]*array) 80 | print(array) 81 | return(array[0]) 82 | 83 | def alpha(t,num = .5, power = .85): 84 | return(num/(t**power)) 85 | 86 | def beta(t,num = .2, power = .001): 87 | return(num/(t**power)) 88 | 89 | def lEqual(nodes, eps = .00001): 90 | flag = True 91 | for i in np.arange(0,len(nodes)-1): 92 | for j in np.arange(i+1,len(nodes)): 93 | if abs(nodes[i].lamda - nodes[j].lamda) > eps: 94 | flag = False 95 | return(flag) 96 | return(flag) 97 | 98 | def run(y,anum,apow,bnum,bpow): 99 | itt = 1 100 | maxitt = 100000 101 | eps = .00013 102 | lhist = [] 103 | xhist = [] 104 | x = [node.x for node in generators] 105 | xhist.append(x) 106 | ycurr = y -x 107 | while (abs(np.sum(x) - np.sum(p)) > eps and itt < maxitt) or itt == 1: 108 | xold = copy.copy(xhist[-1]) 109 | epsilon = 1/19500 110 | lams = [node.lamda for node in generators] 111 | for node in generators: 112 | node.lamda = np.dot(Rp[node.pos,:],lams) + epsilon*y[node.pos] 113 | node.x = node.solvex(node.lamda) 114 | ycurr[node.pos] = np.dot(Rq[node.pos,:],ycurr) - (node.x - xold[node.pos]) 115 | itt += 1 116 | x = [node.x for node in generators] 117 | lams = [node.lamda for node in generators] 118 | xhist.append(x) 119 | lhist.append(lams) 120 | lavg = np.mean(lams) 121 | xtot = np.sum(x) 122 | return((itt,x,lams,xhist,[np.sum(n) - np.sum(p) for n in xhist])) 123 | 124 | P = np.array([0,0,0,.5,1.4,1.1,.9,.2]) 125 | P_tot = np.sum(P) 126 | Q = np.array([1/3,1/5,0,1/4,0,0,0,0,1/3,1/5,1/5,0,0,1/5,0,1/3,0,1/5,1/5,0,0,1/5,1/3,1/3,1/3,0,0,1/4,1/4,1/5,0,0,0,0,0,1/4,1/4,1/5,1/3,0,0,1/5,1/5,1/4,1/4,1/5,0,0,0,0,1/5,0,1/4,0,1/3,0,0,1/5,1/5,0,0,0,0,1/3]) 127 | Q = Q.reshape((8,8)) 128 | AQ = Q > 0 129 | Rq = np.array([1/2,1/3,0,1/2,1/3,1/2,0,1/3,1/2]).reshape((3,3)) 130 | Rp = np.array([1/2,1/2,0,1/3,1/3,1/3,0,1/2,1/2]).reshape((3,3)) 131 | AR = Rq > 0 132 | 133 | n1 = GNode(-1,3,2.1,0,0) 134 | n2 = GNode(-1,2,1.0,0,1) 135 | n3 = GNode(-1,2,5.0,0,2) 136 | n4 = SNode(1,3) 137 | n5 = SNode(1.4,4) 138 | n6 = SNode(1.1,5) 139 | n7 = SNode(.9,6) 140 | n8 = SNode(.2,7) 141 | nodes = [n1,n2,n3,n4,n5,n6,n7,n8] 142 | generators = [n1,n2,n3] 143 | lbounds = np.array([node.llim for node in generators]) 144 | ubounds = np.array([node.ulim for node in generators]) 145 | numnodes = len(nodes) 146 | numgens = len(generators) 147 | eps = .0001 148 | diameter = 2 149 | 150 | p = P 151 | ptot = np.sum(p) 152 | [p,phist] = consensus(p,Q) 153 | 154 | sold = np.zeros((8,)) 155 | for i in np.arange(0,numgens): 156 | sold[i] = p[i] 157 | snew = sold 158 | [snew,hist] = consensus(snew,Q) 159 | 160 | ynew = p*p/snew 161 | [y,yhist] = consensus(ynew[0:3],Rq) 162 | 163 | for i in np.arange(0,numgens): 164 | generators[i].p = y[i] 165 | 166 | (itt,x,lams,xhist,xsums) = run(y,.2,.01,.2,.00001) 167 | plt.plot(xhist) 168 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Distributed_SmartGrid_Algorithms 2 | 3 | ##### This Repository contains implementation of 3 different algorithms for Economic Dispatch In Smart-Grids. 4 | The Motivation for this was to explore and compare different ways of optimizing energy management using only local information in Micro-Grids for a Final Project in EECS 647: *Distributed Optimization* 5 | 6 | #### Algorithms: 7 | * [Bisection](http://ieeexplore.ieee.org/document/6987376/#full-text-section) 8 | * [Consensus + Innovation](http://ieeexplore.ieee.org/document/6345156/) 9 | * [Proportional Control](http://ieeexplore.ieee.org/document/6560423/) 10 | --------------------------------------------------------------------------------