1 #------------------------------------------------------------------------------
2 # Copyright (C) 1996-2010 Power System Engineering Research Center (PSERC)
3 # Copyright (C) 2007-2010 Richard Lincoln
5 # Licensed under the Apache License, Version 2.0 (the "License");
6 # you may not use this file except in compliance with the License.
7 # You may obtain a copy of the License at
9 # http://www.apache.org/licenses/LICENSE-2.0
11 # Unless required by applicable law or agreed to in writing, software
12 # distributed under the License is distributed on an "AS IS" BASIS,
13 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 # See the License for the specific language governing permissions and
15 # limitations under the License.
16 #------------------------------------------------------------------------------
18 """ Defines a generalised OPF solver and an OPF model.
20 Based on opf.m from MATPOWER by Ray Zimmerman, developed at PSERC Cornell.
21 See U{http://www.pserc.cornell.edu/matpower/} for more info.
24 #------------------------------------------------------------------------------
26 #------------------------------------------------------------------------------
34 array
, pi
, diff
, Inf
, ones
, r_
, float64
, zeros
, arctan2
, sin
, cos
36 from scipy
.sparse
import lil_matrix
, csr_matrix
, hstack
38 from util
import _Named
, fair_max
39 from case
import REFERENCE
40 from generator
import PW_LINEAR
41 from solver
import DCOPFSolver
, PIPSSolver
43 #------------------------------------------------------------------------------
45 #------------------------------------------------------------------------------
47 logger
= logging
.getLogger(__name__
)
49 #------------------------------------------------------------------------------
51 #------------------------------------------------------------------------------
54 """ Defines a generalised OPF solver.
56 Based on opf.m from MATPOWER by Ray Zimmerman, developed at PSERC
57 Cornell. See U{http://www.pserc.cornell.edu/matpower/} for more info.
60 def __init__(self
, case
, dc
=True, ignore_ang_lim
=True, opt
=None):
61 """ Initialises a new OPF instance.
63 #: Case under optimisation.
66 #: Use DC power flow formulation.
69 #: Ignore angle difference limits for branches even if specified.
70 self
.ignore_ang_lim
= ignore_ang_lim
72 #: Solver options (See pips.py for futher details).
73 self
.opt
= {} if opt
is None else opt
75 #--------------------------------------------------------------------------
77 #--------------------------------------------------------------------------
79 def solve(self
, solver_klass
=None):
80 """ Solves an optimal power flow and returns a results dictionary.
85 # Build an OPF model with variables and constraints.
86 om
= self
._construct
_opf
_model
(self
.case
)
88 return {"converged": False, "output": {"message": "No Ref Bus."}}
90 # Call the specific solver.
91 # if self.opt["verbose"]:
92 # print '\nPYLON Version %s, %s', "0.4.2", "April 2010"
93 if solver_klass
is not None:
94 result
= solver_klass(om
, opt
=self
.opt
).solve()
96 # if self.opt["verbose"]:
97 # print ' -- DC Optimal Power Flow\n'
98 result
= DCOPFSolver(om
, opt
=self
.opt
).solve()
100 # if self.opt["verbose"]:
101 # print ' -- AC Optimal Power Flow\n'
102 result
= PIPSSolver(om
, opt
=self
.opt
).solve()
104 result
["elapsed"] = time() - t0
106 if self
.opt
.has_key("verbose"):
107 if self
.opt
["verbose"]:
108 logger
.info("OPF completed in %.3fs." % result
["elapsed"])
112 #--------------------------------------------------------------------------
114 #--------------------------------------------------------------------------
116 def _construct_opf_model(self
, case
):
117 """ Returns an OPF model.
119 # Zero the case result attributes.
122 base_mva
= case
.base_mva
124 # Check for one reference bus.
125 oneref
, refs
= self
._ref
_check
(case
)
126 if not oneref
: #return {"status": "error"}
129 # Remove isolated components.
130 bs
, ln
, gn
= self
._remove
_isolated
(case
)
132 # Update bus indexes.
133 self
.case
.index_buses(bs
)
135 # Convert single-block piecewise-linear costs into linear polynomial.
136 gn
= self
._pwl
1_to
_poly
(gn
)
138 # Set-up initial problem variables.
139 Va
= self
._get
_voltage
_angle
_var
(refs
, bs
)
140 Pg
= self
._get
_pgen
_var
(gn
, base_mva
)
142 if self
.dc
: # DC model.
143 # Get the susceptance matrices and phase shift injection vectors.
144 B
, Bf
, Pbusinj
, Pfinj
= self
.case
.makeBdc(bs
, ln
)
146 # Power mismatch constraints (B*Va + Pg = Pd).
147 Pmis
= self
._power
_mismatch
_dc
(bs
, gn
, B
, Pbusinj
, base_mva
)
149 # Branch flow limit constraints.
150 Pf
, Pt
= self
._branch
_flow
_dc
(ln
, Bf
, Pfinj
, base_mva
)
152 # Set-up additional AC-OPF problem variables.
153 Vm
= self
._get
_voltage
_magnitude
_var
(bs
, gn
)
154 Qg
= self
._get
_qgen
_var
(gn
, base_mva
)
156 Pmis
, Qmis
, Sf
, St
= self
._nln
_constraints
(len(bs
), len(ln
))
158 vl
= self
._const
_pf
_constraints
(gn
, base_mva
)
160 # TODO: Generator PQ capability curve constraints.
161 # PQh, PQl = self._pq_capability_curve_constraints(gn)
163 # Branch voltage angle difference limits.
164 ang
= self
._voltage
_angle
_diff
_limit
(bs
, ln
)
168 constraints
= [Pmis
, Pf
, Pt
, ang
]
170 vars = [Va
, Vm
, Pg
, Qg
]
171 constraints
= [Pmis
, Qmis
, Sf
, St
, #PQh, PQL,
174 # Piece-wise linear generator cost constraints.
175 y
, ycon
= self
._pwl
_gen
_costs
(gn
, base_mva
)
179 constraints
.append(ycon
)
181 # Add variables and constraints to the OPF model object.
184 opf
.add_constraints(constraints
)
186 if self
.dc
: # user data
193 def _ref_check(self
, case
):
194 """ Checks that there is only one reference bus.
196 refs
= [bus
._i
for bus
in case
.buses
if bus
.type == REFERENCE
]
201 logger
.error("OPF requires a single reference bus.")
205 def _remove_isolated(self
, case
):
206 """ Returns non-isolated case components.
208 # case.deactivate_isolated()
209 buses
= case
.connected_buses
210 branches
= case
.online_branches
211 gens
= case
.online_generators
213 return buses
, branches
, gens
216 def _pwl1_to_poly(self
, generators
):
217 """ Converts single-block piecewise-linear costs into linear
221 if (g
.pcost_model
== PW_LINEAR
) and (len(g
.p_cost
) == 2):
226 #--------------------------------------------------------------------------
227 # Optimisation variables:
228 #--------------------------------------------------------------------------
230 def _get_voltage_angle_var(self
, refs
, buses
):
231 """ Returns the voltage angle variable set.
233 Va
= array([b
.v_angle
* (pi
/ 180.0) for b
in buses
])
235 Vau
= Inf
* ones(len(buses
))
240 return Variable("Va", len(buses
), Va
, Val
, Vau
)
243 def _get_voltage_magnitude_var(self
, buses
, generators
):
244 """ Returns the voltage magnitude variable set.
246 Vm
= array([b
.v_magnitude
for b
in buses
])
248 # For buses with generators initialise Vm from gen data.
250 Vm
[g
.bus
._i
] = g
.v_magnitude
252 Vmin
= array([b
.v_min
for b
in buses
])
253 Vmax
= array([b
.v_max
for b
in buses
])
255 return Variable("Vm", len(buses
), Vm
, Vmin
, Vmax
)
258 def _get_pgen_var(self
, generators
, base_mva
):
259 """ Returns the generator active power set-point variable.
261 Pg
= array([g
.p
/ base_mva
for g
in generators
])
263 Pmin
= array([g
.p_min
/ base_mva
for g
in generators
])
264 Pmax
= array([g
.p_max
/ base_mva
for g
in generators
])
266 return Variable("Pg", len(generators
), Pg
, Pmin
, Pmax
)
269 def _get_qgen_var(self
, generators
, base_mva
):
270 """ Returns the generator reactive power variable set.
272 Qg
= array([g
.q
/ base_mva
for g
in generators
])
274 Qmin
= array([g
.q_min
/ base_mva
for g
in generators
])
275 Qmax
= array([g
.q_max
/ base_mva
for g
in generators
])
277 return Variable("Qg", len(generators
), Qg
, Qmin
, Qmax
)
279 #--------------------------------------------------------------------------
281 #--------------------------------------------------------------------------
283 def _nln_constraints(self
, nb
, nl
):
284 """ Returns non-linear constraints for OPF.
286 Pmis
= NonLinearConstraint("Pmis", nb
)
287 Qmis
= NonLinearConstraint("Qmis", nb
)
288 Sf
= NonLinearConstraint("Sf", nl
)
289 St
= NonLinearConstraint("St", nl
)
291 return Pmis
, Qmis
, Sf
, St
294 def _power_mismatch_dc(self
, buses
, generators
, B
, Pbusinj
, base_mva
):
295 """ Returns the power mismatch constraint (B*Va + Pg = Pd).
297 nb
, ng
= len(buses
), len(generators
)
298 # Negative bus-generator incidence matrix.
299 gen_bus
= array([g
.bus
._i
for g
in generators
])
300 neg_Cg
= csr_matrix((-ones(ng
), (gen_bus
, range(ng
))), (nb
, ng
))
302 Amis
= hstack([B
, neg_Cg
], format
="csr")
304 Pd
= array([bus
.p_demand
for bus
in buses
])
305 Gs
= array([bus
.g_shunt
for bus
in buses
])
307 bmis
= -(Pd
- Gs
) / base_mva
- Pbusinj
309 return LinearConstraint("Pmis", Amis
, bmis
, bmis
, ["Va", "Pg"])
312 def _branch_flow_dc(self
, branches
, Bf
, Pfinj
, base_mva
):
313 """ Returns the branch flow limit constraint. The real power flows
314 at the from end the lines are related to the bus voltage angles by
315 Pf = Bf * Va + Pfinj.
317 # Indexes of constrained lines.
318 il
= array([i
for i
,l
in enumerate(branches
) if 0.0 < l
.rate_a
< 1e10
])
319 lpf
= -Inf
* ones(len(il
))
320 rate_a
= array([l
.rate_a
/ base_mva
for l
in branches
])
321 upf
= rate_a
[il
] - Pfinj
[il
]
322 upt
= rate_a
[il
] + Pfinj
[il
]
324 Pf
= LinearConstraint("Pf", Bf
[il
, :], lpf
, upf
, ["Va"])
325 Pt
= LinearConstraint("Pt", -Bf
[il
, :], lpf
, upt
, ["Va"])
330 def _const_pf_constraints(self
, gn
, base_mva
):
331 """ Returns a linear constraint enforcing constant power factor for
334 The power factor is derived from the original value of Pmin and either
335 Qmin (for inductive loads) or Qmax (for capacitive loads). If both Qmin
336 and Qmax are zero, this implies a unity power factor without the need
337 for an additional constraint.
339 ivl
= array([i
for i
, g
in enumerate(gn
)
340 if g
.is_load
and (g
.q_min
!= 0.0 or g
.q_max
!= 0.0)])
341 vl
= [gn
[i
] for i
in ivl
]
345 Pg
= array([g
.p
for g
in vl
]) / base_mva
346 Qg
= array([g
.q
for g
in vl
]) / base_mva
347 Pmin
= array([g
.p_min
for g
in vl
]) / base_mva
348 Qmin
= array([g
.q_min
for g
in vl
]) / base_mva
349 Qmax
= array([g
.q_max
for g
in vl
]) / base_mva
351 # At least one of the Q limits must be zero (corresponding to Pmax==0).
353 if g
.qmin
!= 0.0 and g
.q_max
!= 0.0:
354 logger
.error("Either Qmin or Qmax must be equal to zero for "
355 "each dispatchable load.")
357 # Initial values of PG and QG must be consistent with specified power
358 # factor. This is to prevent a user from unknowingly using a case file
359 # which would have defined a different power factor constraint under a
360 # previous version which used PG and QG to define the power factor.
361 Qlim
= (Qmin
== 0.0) * Qmax
+ (Qmax
== 0.0) * Qmin
362 if any( abs(Qg
- Pg
* Qlim
/ Pmin
) > 1e-6 ):
363 logger
.error("For a dispatchable load, PG and QG must be "
364 "consistent with the power factor defined by "
365 "PMIN and the Q limits.")
367 # Make Avl, lvl, uvl, for lvl <= Avl * r_[Pg, Qg] <= uvl
371 pftheta
= arctan2(yy
, xx
)
374 ii
= array([range(nvl
), range(nvl
)])
375 jj
= r_
[ivl
, ivl
+ ng
]
376 Avl
= csr_matrix(r_
[pc
, qc
], (ii
, jj
), (nvl
, 2 * ng
))
380 Avl
= zeros((0, 2 * ng
))
384 return LinearConstraint("vl", Avl
, lvl
, uvl
, ["Pg", "Qg"])
387 def _voltage_angle_diff_limit(self
, buses
, branches
):
388 """ Returns the constraint on the branch voltage angle differences.
392 if not self
.ignore_ang_lim
:
393 iang
= [i
for i
, b
in enumerate(branches
)
394 if (b
.ang_min
and (b
.ang_min
> -360.0))
395 or (b
.ang_max
and (b
.ang_max
< 360.0))]
396 iangl
= array([i
for i
, b
in enumerate(branches
)
397 if b
.ang_min
is not None])[iang
]
398 iangh
= array([i
for i
, b
in enumerate(branches
)
399 if b
.ang_max
is not None])[iang
]
403 ii
= range(nang
) + range(nang
)
404 jjf
= array([b
.from_bus
._i
for b
in branches
])[iang
]
405 jjt
= array([b
.to_bus
._i
for b
in branches
])[iang
]
407 Aang
= csr_matrix(r_
[ones(nang
), -ones(nang
)], (ii
, jj
))
408 uang
= Inf
* ones(nang
)
410 lang
[iangl
] = array([b
.ang_min
* (pi
/ 180.0)
411 for b
in branches
])[iangl
]
412 uang
[iangh
] = array([b
.ang_max
* (pi
/ 180.0)
413 for b
in branches
])[iangh
]
415 # Aang = csr_matrix((0, nb), dtype=float64)
416 # lang = array([], dtype=float64)
417 # uang = array([], dtype=float64)
418 Aang
= zeros((0, nb
))
422 # Aang = csr_matrix((0, nb), dtype=float64)
423 # lang = array([], dtype=float64)
424 # uang = array([], dtype=float64)
425 # iang = array([], dtype=float64)
426 Aang
= zeros((0, nb
))
430 return LinearConstraint("ang", Aang
, lang
, uang
, ["Va"])
433 def _pwl_gen_costs(self
, generators
, base_mva
):
434 """ Returns the basin constraints for piece-wise linear gen cost
435 variables. CCV cost formulation expressed as Ay * x <= by.
437 Based on makeAy.m from MATPOWER by C. E. Murillo-Sanchez, developed at
438 PSERC Cornell. See U{http://www.pserc.cornell.edu/matpower/} for more
442 gpwl
= [g
for g
in generators
if g
.pcost_model
== PW_LINEAR
]
443 # nq = len([g for g in gpwl if g.qcost_model is not None])
446 pgbas
= 0 # starting index within x for active sources
447 nq
= 0 # number of Qg vars
448 # qgbas = None # index of 1st Qg column in Ay
449 ybas
= ng
# starting index within x for y variables
453 # qgbas = ng + 1 # index of 1st Qg column in Ay
456 # Number of extra y variables.
462 # Total number of cost points.
463 nc
= len([co
for gn
in gpwl
for co
in gn
.p_cost
])
464 # Ay = lil_matrix((nc - ny, ybas + ny))
465 # Fill rows and then transpose.
466 Ay
= lil_matrix((ybas
+ ny
, nc
- ny
))
471 for i
, g
in enumerate(gpwl
):
472 # Number of cost points: segments = ns-1
475 p
= array([x
/ base_mva
for x
, c
in g
.p_cost
])
476 c
= array([c
for x
, c
in g
.p_cost
])
477 m
= diff(c
) / diff(p
) # Slopes for Pg (or Qg).
480 raise ValueError, "Bad Pcost data: %s (%s)" % (p
, g
.name
)
481 logger
.error("Bad Pcost data: %s" % p
)
483 b
= m
* p
[:ns
-1] - c
[:ns
-1] # rhs
487 # sidx = qgbas + (i-ng) - 1 # this was for a q cost
489 # sidx = pgbas + i - 1 # this was for a p cost
491 Ay
[pgbas
+ i
, k
:k
+ ns
- 1] = m
493 # FIXME: Repeat for Q costs.
495 # Now fill the y rows with -1's
496 Ay
[ybas
+ j
, k
:k
+ ns
- 1] = -ones(ns
-1)
501 y
= Variable("y", ny
)
503 # Transpose Ay since lil_matrix stores in rows.
505 ycon
= LinearConstraint("ycon", Ay
.T
, None, by
, ["Pg", "y"])
507 ycon
= LinearConstraint("ycon", Ay
.T
, None, by
, ["Pg", "Qg","y"])
511 #------------------------------------------------------------------------------
513 #------------------------------------------------------------------------------
516 """ The standard OPF formulation has no mechanism for completely shutting
517 down generators which are very expensive to operate. Instead they are
518 simply dispatched at their minimum generation limits. PYLON includes the
519 capability to run an optimal power flow combined with a unit decommitment
520 for a single time period, which allows it to shut down these expensive
521 units and find a least cost commitment and dispatch.
523 Solves a combined unit decommitment and optimal power flow for a
524 single time period. Uses an algorithm similar to dynamic programming.
525 It proceeds through a sequence of stages, where stage N has N generators
526 shut down, starting with N=0. In each stage, it forms a list of candidates
527 (gens at their Pmin limits) and computes the cost with each one of them
528 shut down. It selects the least cost case as the starting point for the
529 next stage, continuing until there are no more candidates to be shut down
530 or no more improvement can be gained by shutting something down.
532 Based on uopf.m from MATPOWER by Ray Zimmerman, developed at PSERC
533 Cornell. See U{http://www.pserc.cornell.edu/matpower/} for more info.
536 - Ray Zimmerman, "MATPOWER User's Manual", MATPOWER, PSERC Cornell,
537 version 3.2, U{http://www.pserc.cornell.edu/matpower/}, Sept, 2007
540 def solve(self
, solver_klass
=None):
541 """ Solves the combined unit decommitment / optimal power flow problem.
544 generators
= case
.online_generators
546 logger
.info("Solving OPF with unit de-commitment [%s]." % case
.name
)
550 # 1. Begin at stage zero (N = 0), assuming all generators are on-line
551 # with all limits in place. At most one generator shutdown per stage.
554 # Check for sum(p_min) > total load, decommit as necessary.
555 online
= [g
for g
in generators
if not g
.is_load
]
556 online_vload
= [g
for g
in generators
if g
.is_load
]
558 # Total dispatchable load capacity.
559 vload_capacity
= sum([g
.p_min
for g
in online_vload
])
560 # Total load capacity.
561 load_capacity
= sum([b
.p_demand
for b
in case
.buses
]) - vload_capacity
563 # Minimum total online generation capacity.
564 p_min_tot
= sum([g
.p_min
for g
in online
])
566 # Shutdown the most expensive units until the minimum generation
567 # capacity is less than the total load capacity.
568 while p_min_tot
> load_capacity
:
570 logger
.debug("De-commitment stage %d." % i_stage
)
572 # Find generator with the maximum average cost at Pmin.
573 avg_pmin_cost
= [g
.total_cost(g
.p_min
) / g
.p_min
for g
in online
]
574 # Select at random from maximal generators with equal cost.
575 g_idx
, _
= fair_max(avg_pmin_cost
)
576 generator
= online
[g_idx
]
578 logger
.info("Shutting down generator [%s] to satisfy all "
579 "p_min limits." % generator
.name
)
581 # Shut down most expensive unit.
582 generator
.online
= False
584 # Update minimum generation capacity for while loop.
585 online
= [g
for g
in case
.online_generators
if not g
.is_load
]
586 p_min_tot
= sum([g
.p_min
for g
in online
])
588 # 2. Solve a normal OPF and save the solution as the current best.
589 solution
= super(UDOPF
, self
).solve(solver_klass
)
591 logger
.debug("Initial system cost: $%.3f" % solution
["f"])
593 if not solution
["converged"] == True:
594 logger
.error("Non-convergent UDOPF [%s]." %
595 solution
["output"]["message"])
598 # 3. Go to the next stage, N = N + 1. Using the best solution from the
599 # previous stage as the base case for this stage, ...
601 # Best case so far. A list of the on-line status of all generators.
602 overall_online
= [g
.online
for g
in case
.generators
]
603 # The objective function value is the total system cost.
604 overall_cost
= solution
["f"]
606 # Best case for this stage.
607 stage_online
= overall_online
608 stage_cost
= overall_cost
610 # Shutdown at most one generator per stage.
612 # 4. Form a candidate list of generators with minimum
613 # generation limits binding.
615 # Activate generators according to the stage best.
616 for i
, generator
in enumerate(case
.generators
):
617 generator
.online
= stage_online
[i
]
619 # Get candidates for shutdown. Lagrangian multipliers are often
620 # very small so we round to four decimal places.
621 candidates
= [g
for g
in case
.online_generators
if \
622 (round(g
.mu_pmin
, 4) > 0.0) and (g
.p_min
> 0.0)]
624 if len(candidates
) == 0:
627 # Assume no improvement during this stage.
631 logger
.debug("De-commitment stage %d." % i_stage
)
633 for candidate
in candidates
:
634 # 5. For each generator on the candidate list, solve an OPF to
635 # find the total system cost with the generator shut down.
637 # Activate generators according to the stage best.
638 for i
, generator
in enumerate(case
.generators
):
639 generator
.online
= stage_online
[i
]
641 # Shutdown candidate generator.
642 candidate
.online
= False
644 logger
.debug("Solving OPF with generator '%s' shutdown." %
648 solution
= super(UDOPF
, self
).solve(solver_klass
)
650 # Compare total system costs for improvement.
651 if solution
["converged"] == True \
652 and (solution
["f"] < overall_cost
):
653 logger
.debug("System cost improvement: $%.3f ($%.3f)" %
654 (stage_cost
- solution
["f"], solution
["f"]))
655 # 6. Replace the current best solution with this one if
656 # it has a lower cost.
657 overall_online
= [g
.online
for g
in case
.generators
]
658 overall_cost
= solution
["f"]
659 best_candidate
= candidate
660 # Check for further decommitment.
663 logger
.debug("Candidate OPF failed [%s]." %
664 solution
["output"]["message"])
666 # Reactivate the candidate before deactivating the next.
667 # candidate.online = True
670 # Decommits at this stage did not help.
673 # 7. If any of the candidate solutions produced an improvement,
676 # Shutting something else down helps, so let's keep going.
677 logger
.info("Shutting down generator '%s'.",
680 stage_online
= overall_online
681 stage_cost
= overall_cost
683 # 8. Use the best overall solution as the final solution.
684 for i
, generator
in enumerate(case
.generators
):
685 generator
.online
= overall_online
[i
]
687 # One final solve using the best case to ensure all results are
689 solution
= super(UDOPF
, self
).solve(solver_klass
)
691 logger
.debug("UDOPF system cost: $%.3f" % solution
["f"])
693 # Compute elapsed time and log it.
694 elapsed
= time() - t0
696 plural
= "" if i_stage
== 1 else "s"
697 logger
.info("Unit decommitment OPF solved in %.3fs (%d decommitment "
698 "stage%s)." % (elapsed
, i_stage
, plural
))
702 #------------------------------------------------------------------------------
704 #------------------------------------------------------------------------------
706 class OPFModel(object):
707 """ Defines a model for optimal power flow.
709 Based on @opf_model in MATPOWER by Ray Zimmerman, developed at PSERC
710 Cornell. See U{http://www.pserc.cornell.edu/matpower/} for more info.
713 def __init__(self
, case
):
714 #: Case to which the model relates.
716 #: Optimisation variables.
718 #: Linear constraints.
719 self
.lin_constraints
= []
720 #: Non-linear constraints.
721 self
.nln_constraints
= []
722 #: User defined costs.
728 return sum([v
.N
for v
in self
.vars])
731 def add_var(self
, var
):
732 """ Adds a variable to the model.
734 if var
.name
in [v
.name
for v
in self
.vars]:
735 logger
.error("Variable set named '%s' already exists." % var
.name
)
739 var
.iN
= self
.var_N
+ var
.N
- 1
740 self
.vars.append(var
)
743 def add_vars(self
, vars):
744 """ Adds a set of variables to the model.
750 def get_var(self
, name
):
751 """ Returns the variable set with the given name.
753 for var
in self
.vars:
761 def get_var_N(self
, name
):
762 """ Return the number of variables in the named set.
764 return self
.get_var(name
).N
769 return sum([c
.N
for c
in self
.nln_constraints
])
774 return sum([c
.N
for c
in self
.lin_constraints
])
779 return len(self
.lin_constraints
)
782 def linear_constraints(self
):
783 """ Returns the linear constraints.
786 return None, array([]), array([])
788 A
= lil_matrix((self
.lin_N
, self
.var_N
), dtype
=float64
)
789 l
= -Inf
* ones(self
.lin_N
)
792 for lin
in self
.lin_constraints
:
793 if lin
.N
: # non-zero number of rows to add
794 Ak
= lin
.A
# A for kth linear constrain set
795 i1
= lin
.i1
# starting row index
796 iN
= lin
.iN
# ending row index
797 vsl
= lin
.vs
# var set list
798 kN
= -1 # initialize last col of Ak used
799 Ai
= lil_matrix((lin
.N
, self
.var_N
), dtype
=float64
)
801 var
= self
.get_var(v
)
802 j1
= var
.i1
# starting column in A
803 jN
= var
.iN
# ending column in A
804 k1
= kN
+ 1 # starting column in Ak
805 kN
= kN
+ var
.N
# ending column in Ak
808 # FIXME: Single column slicing broken in lil.
809 for i
in range(Ai
.shape
[0]):
810 Ai
[i
, j1
] = Ak
[i
, k1
]
812 Ai
[:, j1
:jN
+ 1] = Ak
[:, k1
:kN
+ 1]
818 return A
.tocsr(), l
, u
821 def add_constraint(self
, con
):
822 """ Adds a constraint to the model.
824 if isinstance(con
, LinearConstraint
):
826 if con
.name
in [c
.name
for c
in self
.lin_constraints
]:
827 logger
.error("Constraint set named '%s' already exists."
831 con
.i1
= self
.lin_N
# + 1
832 con
.iN
= self
.lin_N
+ N
- 1
836 nv
= nv
+ self
.get_var_N(vs
)
838 logger
.error("Number of columns of A does not match number"
839 " of variables, A is %d x %d, nv = %d", N
, M
, nv
)
840 self
.lin_constraints
.append(con
)
841 elif isinstance(con
, NonLinearConstraint
):
843 if con
.name
in [c
.name
for c
in self
.nln_constraints
]:
844 logger
.error("Constraint set named '%s' already exists."
848 con
.i1
= self
.nln_N
# + 1
849 con
.iN
= self
.nln_N
+ N
850 self
.nln_constraints
.append(con
)
857 def add_constraints(self
, constraints
):
858 """ Adds constraints to the model.
860 for con
in constraints
:
861 self
.add_constraint(con
)
864 def get_lin_constraint(self
, name
):
865 """ Returns the constraint set with the given name.
867 for c
in self
.lin_constraints
:
874 def get_nln_constraint(self
, name
):
875 """ Returns the constraint set with the given name.
877 for c
in self
.nln_constraints
:
885 return sum([c
.N
for c
in self
.costs
])
888 def get_cost_params(self
):
889 """ Returns the cost parameters.
891 return [c
.params
for c
in self
.costs
]
893 #------------------------------------------------------------------------------
895 #------------------------------------------------------------------------------
899 def __init__(self
, name
, N
):
900 #: String identifier.
915 #: Ordered list of sets.
918 #------------------------------------------------------------------------------
920 #------------------------------------------------------------------------------
922 class Variable(_Set
):
923 """ Defines a set of variables.
926 def __init__(self
, name
, N
, v0
=None, vl
=None , vu
=None):
927 """ Initialises a new Variable instance.
929 super(Variable
, self
).__init
__(name
, N
)
931 #: Initial value of the variables. Zero by default.
937 #: Lower bound on the variables. Unbounded below be default.
939 self
.vl
= -Inf
* ones(N
)
943 #: Upper bound on the variables. Unbounded above by default.
945 self
.vu
= Inf
* ones(N
)
949 #------------------------------------------------------------------------------
950 # "LinearConstraint" class:
951 #------------------------------------------------------------------------------
953 class LinearConstraint(_Set
):
954 """ Defines a set of linear constraints.
957 def __init__(self
, name
, AorN
, l
=None, u
=None, vs
=None):
960 super(LinearConstraint
, self
).__init
__(name
, N
)
963 self
.l
= -Inf
* ones(N
) if l
is None else l
964 self
.u
= Inf
* ones(N
) if u
is None else u
967 self
.vs
= [] if vs
is None else vs
969 if (self
.l
.shape
[0] != N
) or (self
.u
.shape
[0] != N
):
970 logger
.error("Sizes of A, l and u must match.")
972 #------------------------------------------------------------------------------
973 # "NonLinearConstraint" class:
974 #------------------------------------------------------------------------------
976 class NonLinearConstraint(_Set
):
977 """ Defines a set of non-linear constraints.
981 #------------------------------------------------------------------------------
983 #------------------------------------------------------------------------------
997 # EOF -------------------------------------------------------------------------