diff --git a/lib/t/+mp/dmce_legacy_dcline_mpc2.m b/lib/t/+mp/dmce_legacy_dcline_mpc2.m new file mode 100644 index 00000000..807ae9d2 --- /dev/null +++ b/lib/t/+mp/dmce_legacy_dcline_mpc2.m @@ -0,0 +1,94 @@ +classdef dmce_legacy_dcline_mpc2 < mp.dmc_element % & mp.dmce_legacy_dcline +% mp.dmce_legacy_dcline_mpc2 - Data model converter element for legacy DC line for |MATPOWER| case v2. + +% MATPOWER +% Copyright (c) 2021-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + + methods + function name = name(obj) + % + name = 'legacy_dcline'; + end + + function df = data_field(obj) + % + df = 'dcline'; + end + + function vmap = table_var_map(obj, dme, mpc) + % + vmap = table_var_map@mp.dmc_element(obj, dme, mpc); + + %% define named indices into data matrices + c = idx_dcline; + + gcip_fcn = @(ob, mpc, spec, vn)dcline_cost_import(ob, mpc, spec, vn); + gcep_fcn = @(ob, dme, mpc, spec, vn, ridx)dcline_cost_export(ob, dme, mpc, spec, vn, ridx); + + %% mapping for each name, default is {'col', []} + vmap.uid = {'IDs'}; %% consecutive IDs, starting at 1 + vmap.name = {'cell', ''}; %% empty char + vmap.status{2} = c.BR_STATUS; + vmap.source_uid = {'cell', ''}; %% empty char + vmap.bus_fr{2} = c.F_BUS; + vmap.bus_to{2} = c.T_BUS; + vmap.loss0{2} = c.LOSS0; + vmap.loss1{2} = c.LOSS1; + vmap.vm_setpoint_fr{2} = c.VF; + vmap.vm_setpoint_to{2} = c.VT; + vmap.p_fr_lb{2} = c.PMIN; + vmap.p_fr_ub{2} = c.PMAX; + vmap.q_fr_lb{2} = c.QMINF; + vmap.q_fr_ub{2} = c.QMAXF; + vmap.q_to_lb{2} = c.QMINT; + vmap.q_to_ub{2} = c.QMAXT; + vmap.p_fr{2} = c.PF; + vmap.q_fr{2} = c.QF; + vmap.p_to{2} = c.PT; + vmap.q_to{2} = c.QT; + if isfield(vmap, 'cost') + vmap.cost = {'fcn', gcip_fcn, gcep_fcn}; + vmap.mu_p_fr_lb{2} = c.MU_PMIN; + vmap.mu_p_fr_ub{2} = c.MU_PMAX; + vmap.mu_q_fr_lb{2} = c.MU_QMINF; + vmap.mu_q_fr_ub{2} = c.MU_QMAXF; + vmap.mu_q_to_lb{2} = c.MU_QMINT; + vmap.mu_q_to_ub{2} = c.MU_QMAXT; + end + end + + function dt = default_export_data_table(obj, spec) + % + + %% define named indices into data matrices + c = idx_dcline; + + nr = obj.default_export_data_nrows(spec); + dt = zeros(nr, c.QMAXT); + end + + function val = dcline_cost_import(obj, mpc, spec, vn) + % + if isfield(mpc, 'dclinecost') && spec.nr + val = mp.dmce_gen_mpc2.gencost2cost_table(mpc.dclinecost); + else + val = []; + end + end + + function mpc = dcline_cost_export(obj, dme, mpc, spec, vn, ridx) + % + + if dme.have_cost() + cost = mp.dmce_gen_mpc2.cost_table2gencost( ... + [], dme.tab.cost, ridx); + mpc.dclinecost(1:spec.nr, 1:size(cost, 2)) = cost; + end + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/dme_legacy_dcline.m b/lib/t/+mp/dme_legacy_dcline.m new file mode 100644 index 00000000..34b5d365 --- /dev/null +++ b/lib/t/+mp/dme_legacy_dcline.m @@ -0,0 +1,204 @@ +classdef dme_legacy_dcline < mp.dm_element +% mp.dme_legacy_dcline - Data model element for legacy DC line. + +% MATPOWER +% Copyright (c) 2020-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + + properties + fbus % bus index vector for "from" port (port 1) (all DC lines) + tbus % bus index vector for "to" port (port 2) (all DC lines) + fbus_on % vector of "from" bus indices into online buses (in-service DC lines) + tbus_on % vector of "to" bus indices into online buses (in-service DC lines) + loss0 % constant term of loss function (p.u.) (in-service DC lines) + loss1 % linear coefficient of loss function (in-service DC lines) + p_fr_start % initial active power (p.u.) at "from" port (in-service DC lines) + p_to_start % initial active power (p.u.) at "to" port (in-service DC lines) + q_fr_start % initial reactive power (p.u.) at "from" port (in-service DC lines) + q_to_start % initial reactive power (p.u.) at "to" port (in-service DC lines) + vm_setpoint_fr % from bus voltage magnitude setpoint (p.u.) (in-service DC lines) + vm_setpoint_to % to bus voltage magnitude setpoint (p.u.) (in-service DC lines) + p_fr_lb % p.u. lower bound on active power flow at "from" port (in-service DC lines) + p_fr_ub % p.u. upper bound on active power flow at "from" port (in-service DC lines) + q_fr_lb % p.u. lower bound on reactive power flow at "from" port (in-service DC lines) + q_fr_ub % p.u. upper bound on reactive power flow at "from" port (in-service DC lines) + q_to_lb % p.u. lower bound on reactive power flow at "to" port (in-service DC lines) + q_to_ub % p.u. upper bound on reactive power flow at "to" port (in-service DC lines) + end %% properties + + methods + function name = name(obj) + % + name = 'legacy_dcline'; + end + + function label = label(obj) + % + label = 'DC Line'; + end + + function label = labels(obj) + % + label = 'DC Lines'; + end + + function name = cxn_type(obj) + % + name = 'bus'; + end + + function name = cxn_idx_prop(obj) + % + name = {'fbus', 'tbus'}; + end + + function names = main_table_var_names(obj) + % + names = horzcat( main_table_var_names@mp.dm_element(obj), ... + {'bus_fr', 'bus_to', 'loss0', 'loss1', ... + 'vm_setpoint_fr', 'vm_setpoint_to', ... + 'p_fr_lb', 'p_fr_ub', ... + 'q_fr_lb', 'q_fr_ub', 'q_to_lb', 'q_to_ub', ... + 'p_fr', 'q_fr', 'p_to', 'q_to'} ); + end + + function vars = export_vars(obj) + % + vars = {'p_fr', 'q_fr', 'p_to', 'q_to'}; + end + + function s = export_vars_offline_val(obj) + % + + s = export_vars_offline_val@mp.dm_element(obj); %% call parent + s.p_fr = 0; + s.q_fr = 0; + s.p_to = 0; + s.q_to = 0; + end + + function TorF = have_cost(obj) + % + TorF = 0; + end + + function obj = initialize(obj, dm) + % + initialize@mp.dm_element(obj, dm); %% call parent + + %% get bus mapping info + b2i = dm.elements.bus.ID2i; %% bus num to idx mapping + + %% set bus index vectors for port connectivity + obj.fbus = b2i(obj.tab.bus_fr); + obj.tbus = b2i(obj.tab.bus_to); + end + + function obj = update_status(obj, dm) + % + + %% get bus status info + bus_dme = dm.elements.bus; + bs = bus_dme.tab.status; %% bus status + + %% update status of branches connected to isolated/offline buses + obj.tab.status = obj.tab.status & bs(obj.fbus) & ... + bs(obj.tbus); + + %% call parent to fill in on/off + update_status@mp.dm_element(obj, dm); + + %% for all online DC lines ... + %% ... set terminal buses (except ref) to PV type + idx = [obj.fbus(obj.on); obj.tbus(obj.on)]; %% all terminal buses + idx(bus_dme.type(idx) == mp.NODE_TYPE.REF) = []; %% except ref + bus_dme.set_bus_type_pv(dm, idx); + + %% set bus_dme.vm_control + obj.fbus_on = bus_dme.i2on(obj.fbus(obj.on)); + obj.tbus_on = bus_dme.i2on(obj.tbus(obj.on)); + bus_dme.vm_control(obj.fbus_on) = 1; + bus_dme.vm_control(obj.tbus_on) = 1; + end + + function obj = apply_vm_setpoints(obj, dm) + % + + % set starting bus voltage, if bus is voltage-controlled + bus_dme = dm.elements.bus; + i_fr = find(bus_dme.vm_control(obj.fbus_on)); + i_to = find(bus_dme.vm_control(obj.tbus_on)); + bus_dme.vm_start(obj.fbus_on(i_fr)) = obj.vm_setpoint_fr(i_fr); + bus_dme.vm_start(obj.tbus_on(i_to)) = obj.vm_setpoint_to(i_to); + end + + function obj = build_params(obj, dm) + % + obj.loss0 = obj.tab.loss0(obj.on) / dm.base_mva; + obj.loss1 = obj.tab.loss1(obj.on); + obj.p_fr_start = obj.tab.p_fr(obj.on) / dm.base_mva; + obj.p_to_start = (obj.loss1 - 1) .* obj.p_fr_start + obj.loss0; + obj.q_fr_start = -obj.tab.q_fr(obj.on) / dm.base_mva; + obj.q_to_start = -obj.tab.q_to(obj.on) / dm.base_mva; + obj.vm_setpoint_fr = obj.tab.vm_setpoint_fr(obj.on); + obj.vm_setpoint_to = obj.tab.vm_setpoint_to(obj.on); + obj.p_fr_lb = obj.tab.p_fr_lb(obj.on) / dm.base_mva; + obj.p_fr_ub = obj.tab.p_fr_ub(obj.on) / dm.base_mva; + obj.q_fr_lb = obj.tab.q_fr_lb(obj.on) / dm.base_mva; + obj.q_fr_ub = obj.tab.q_fr_ub(obj.on) / dm.base_mva; + obj.q_to_lb = obj.tab.q_to_lb(obj.on) / dm.base_mva; + obj.q_to_ub = obj.tab.q_to_ub(obj.on) / dm.base_mva; + + obj.apply_vm_setpoints(dm); + end + + function TorF = pp_have_section_sum(obj, mpopt, pp_args) + % + TorF = true; + end + + function obj = pp_data_sum(obj, dm, rows, out_e, mpopt, fd, pp_args) + % + + %% call parent + pp_data_sum@mp.dm_element(obj, dm, rows, out_e, mpopt, fd, pp_args); + + %% print DC line summary + fprintf(fd, ' %-29s %12.2f MW', 'Total DC line losses', ... + sum(obj.tab.p_fr(obj.on)) - sum(obj.tab.p_to(obj.on)) ); + if mpopt.model(1) ~= 'D' %% AC model + fprintf(fd, ' %12.2f MVAr', ... + sum(obj.tab.q_fr(obj.on)) + sum(obj.tab.q_to(obj.on)) ); + end + fprintf(fd, '\n'); + end + + function h = pp_get_headers_det(obj, dm, out_e, mpopt, pp_args) + % + h = [ pp_get_headers_det@mp.dm_element(obj, dm, out_e, mpopt, pp_args) ... + { ' DC Line From To Power Flow (MW) Loss Reactive Inj (MVAr)', ... + ' ID Bus ID Bus ID Status From To (MW) From To', ... + '-------- -------- -------- ------ -------- -------- -------- -------- --------' } ]; + %% 1234567 123456789 123456789 -----1 1234567.90 123456.89 123456.89 123456.89 123456.89 + end + + function TorF = pp_have_section_det(obj, mpopt, pp_args) + % + TorF = true; + end + + function str = pp_data_row_det(obj, dm, k, out_e, mpopt, fd, pp_args) + % + str = sprintf('%7d %9d %9d %6d %10.2f %9.2f %9.2f %9.2f %9.2f', ... + obj.tab.uid(k), obj.tab.bus_fr(k), obj.tab.bus_to(k), ... + obj.tab.status(k), ... + obj.tab.p_fr(k), obj.tab.p_to(k), ... + obj.tab.p_fr(k) - obj.tab.p_to(k), ... + obj.tab.q_fr(k), obj.tab.q_to(k) ); + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/dme_legacy_dcline_opf.m b/lib/t/+mp/dme_legacy_dcline_opf.m new file mode 100644 index 00000000..67eb4dca --- /dev/null +++ b/lib/t/+mp/dme_legacy_dcline_opf.m @@ -0,0 +1,125 @@ +classdef dme_legacy_dcline_opf < mp.dme_legacy_dcline & mp.dme_shared_opf +% mp.dme_legacy_dcline_opf - Data model element for legacy DC line for OPF. + +% MATPOWER +% Copyright (c) 2020-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + + methods + function names = main_table_var_names(obj) + % + names = horzcat( main_table_var_names@mp.dme_legacy_dcline(obj), ... + { 'cost', ... + 'mu_p_fr_lb', 'mu_p_fr_ub', ... + 'mu_q_fr_lb', 'mu_q_fr_ub', ... + 'mu_q_to_lb', 'mu_q_to_ub' } ); + end + + function vars = export_vars(obj) + % + vars = horzcat( export_vars@mp.dme_legacy_dcline(obj), ... + { 'vm_setpoint_fr', 'vm_setpoint_to', ... + 'mu_p_fr_lb', 'mu_p_fr_ub', ... + 'mu_q_fr_lb', 'mu_q_fr_ub', ... + 'mu_q_to_lb', 'mu_q_to_ub' } ); + end + + function s = export_vars_offline_val(obj) + % + + s = export_vars_offline_val@mp.dme_legacy_dcline(obj); %% call parent + s.mu_p_fr_lb = 0; + s.mu_p_fr_ub = 0; + s.mu_q_fr_lb = 0; + s.mu_q_fr_ub = 0; + s.mu_q_to_lb = 0; + s.mu_q_to_ub = 0; + end + + function TorF = have_cost(obj) + % + TorF = 1; + end + + function cost = build_cost_params(obj, dm) + % + if ismember('cost', obj.tab.Properties.VariableNames) + poly = mp.cost_table_utils.poly_params(obj.tab.cost, obj.on, dm.base_mva); + pwl = mp.cost_table_utils.pwl_params(obj.tab.cost, obj.on, dm.base_mva); + + cost = struct( ... + 'poly', poly, ... + 'pwl', pwl ... + ); + else + cost = struct([]); + end + end + + function obj = pretty_print(obj, dm, section, out_e, mpopt, fd, pp_args) + % + switch section + case 'lim' + %% pass flows and limits to parent + p_fr = obj.tab.p_fr; + lb = obj.tab.p_fr_lb; + ub = obj.tab.p_fr_ub; + pp_args.legacy_dcline.flow = struct( 'p_fr', p_fr, ... + 'lb', lb, ... + 'ub', ub ); + end + + pretty_print@mp.dme_legacy_dcline(obj, dm, section, out_e, mpopt, fd, pp_args); + end + + function TorF = pp_have_section_lim(obj, mpopt, pp_args) + % + TorF = true; + end + + function rows = pp_binding_rows_lim(obj, dm, out_e, mpopt, pp_args) + % + flow = pp_args.legacy_dcline.flow; + rows = find( obj.tab.status & ( ... + flow.p_fr < flow.lb + obj.ctol | ... + flow.p_fr > flow.ub - obj.ctol | ... + obj.tab.mu_p_fr_lb > obj.ptol | ... + obj.tab.mu_p_fr_ub > obj.ptol )); + end + + function h = pp_get_headers_lim(obj, dm, out_e, mpopt, pp_args) + % + h = [ pp_get_headers_lim@mp.dme_shared_opf(obj, dm, out_e, mpopt, pp_args) ... + { ' DC Line From To Active Power Flow (MW)', ... + ' ID Bus ID Bus ID mu LB LB p_fr UB mu UB', ... + '-------- -------- -------- --------- ------- ------- ------- ---------' } ]; + %% 1234567 123456789 123456789 123456.890 12345.78 12345.78 12345.78 123456.890 + end + + function str = pp_data_row_lim(obj, dm, k, out_e, mpopt, fd, pp_args) + % + flow = pp_args.legacy_dcline.flow; + + if (flow.p_fr(k) < flow.lb(k) + obj.ctol || ... + obj.tab.mu_p_fr_lb(k) > obj.ptol) + mu_lb = sprintf('%10.3f', obj.tab.mu_p_fr_lb(k)); + else + mu_lb = ' - '; + end + if (flow.p_fr(k) > flow.ub(k) - obj.ctol || ... + obj.tab.mu_p_fr_ub(k) > obj.ptol) + mu_ub = sprintf('%10.3f', obj.tab.mu_p_fr_ub(k)); + else + mu_ub = ' - '; + end + + str = sprintf('%7d %9d %9d %10s %8.2f %8.2f %8.2f %10s', ... + obj.tab.uid(k), obj.tab.bus_fr(k), obj.tab.bus_to(k), ... + mu_lb, flow.lb(k), flow.p_fr(k), flow.ub(k), mu_ub); + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/mme_legacy_dcline.m b/lib/t/+mp/mme_legacy_dcline.m new file mode 100644 index 00000000..8e8949f9 --- /dev/null +++ b/lib/t/+mp/mme_legacy_dcline.m @@ -0,0 +1,18 @@ +classdef (Abstract) mme_legacy_dcline < mp.mm_element +% mp.mme_legacy_dcline - Math model element abstract base class for legacy DC line. + +% MATPOWER +% Copyright (c) 2021-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + + methods + function name = name(obj) + % + name = 'legacy_dcline'; + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/mme_legacy_dcline_opf.m b/lib/t/+mp/mme_legacy_dcline_opf.m new file mode 100644 index 00000000..20a1566e --- /dev/null +++ b/lib/t/+mp/mme_legacy_dcline_opf.m @@ -0,0 +1,102 @@ +classdef (Abstract) mme_legacy_dcline_opf < mp.mme_legacy_dcline +% mp.mme_legacy_dcline_opf - Math model element abstract base class for legacy DC line for OPF. + +% MATPOWER +% Copyright (c) 2021-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + + properties + % struct for cost parameters with fields: + % + % - ``poly`` - polynomial costs for active power, struct with + % fields: + % + % - ``have_quad_cost`` + % - ``i0``, ``i1``, ``i2``, ``i3`` + % - ``k``, ``c``, ``Q`` + % - ``pwl`` - piecewise linear costs for actve power, struct + % with fields: + % + % - ``n``, ``i``, ``A``, ``b`` + cost + end + + methods + function build_cost_params(obj, dm) + % + dme = obj.data_model_element(dm); + obj.cost = dme.build_cost_params(dm); + end + + function obj = add_vars(obj, mm, nm, dm, mpopt) + % + + %% collect/construct all legacy DC line cost parameters + obj.build_cost_params(dm); + + %% piecewise linear costs + if ~isempty(obj.cost) && obj.cost.pwl.n + mm.add_var('ydc', obj.cost.pwl.n); + end + end + + function obj = add_constraints(obj, mm, nm, dm, mpopt) + % + + %% add constraint on active flows and ends of DC line + dme = obj.data_model_element(dm); + A = [spdiags(dme.loss1 - 1, 0, dme.n, dme.n) -speye(dme.n, dme.n)]; + b = -dme.loss0; + mm.add_lin_constraint('dcline_legacy', A, b, b, {'Pdcf', 'Pdct'}); + + %% piecewise linear costs + if ~isempty(obj.cost) && obj.cost.pwl.n + mm.add_lin_constraint('ycondc', obj.cost.pwl.A, [], obj.cost.pwl.b, {'Pdcf', 'ydc'}); + end + + %% call parent + add_constraints@mp.mme_legacy_dcline(obj, mm, nm, dm, mpopt); + end + + function obj = add_costs(obj, mm, nm, dm, mpopt) + % + + if ~isempty(obj.cost) + %% (quadratic) polynomial costs on Pdcf + if obj.cost.poly.have_quad_cost + mm.add_quad_cost('polPdcf', obj.cost.poly.Q, obj.cost.poly.c, obj.cost.poly.k, {'Pdcf'}); + end + + %% (order 3 and higher) polynomial costs on Pg + if ~isempty(obj.cost.poly.i3) + dme = obj.data_model_element(dm); + cost_Pdcf = @(xx)mp.cost_table.poly_cost_fcn( ... + xx, dm.base_mva, dme.tab.cost.poly_coef(dme.on, :), ... + obj.cost.poly.i3); + mm.add_nln_cost('polPdcf', 1, cost_Pdcf, {'Pdcf'}); + end + + %% piecewise linear costs + if obj.cost.pwl.n + mm.add_quad_cost('pwldc', [], ones(obj.cost.pwl.n, 1), 0, {'ydc'}); + end + end + end + + function x0 = interior_x0(obj, mm, nm, dm, x0) + % + + %% set gen cost variables to something feasible + if ~isempty(obj.cost) && obj.cost.pwl.n > 0 + vv = mm.get_idx(); + dme = obj.data_model_element(dm); + maxc = max_pwl_cost(dme.tab.cost); + x0(vv.i1.ydc:vv.iN.ydc) = maxc + 0.1 * abs(maxc); + end + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/mme_legacy_dcline_opf_ac.m b/lib/t/+mp/mme_legacy_dcline_opf_ac.m new file mode 100644 index 00000000..5906edd9 --- /dev/null +++ b/lib/t/+mp/mme_legacy_dcline_opf_ac.m @@ -0,0 +1,54 @@ +classdef mme_legacy_dcline_opf_ac < mp.mme_legacy_dcline_opf +% mp.mme_legacy_dcline_opf_ac - Math model element for legacy DC line for AC OPF. + +% MATPOWER +% Copyright (c) 2021-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + +% properties +% end %% properties + + methods + function obj = data_model_update_on(obj, mm, nm, dm, mpopt) + % + + dme = obj.data_model_element(dm); + nme = obj.network_model_element(nm); + + %% legacy DC line active power + ss = nm.get_idx('state'); + s_fr = nm.soln.z(ss.i1.legacy_dcline(1):ss.iN.legacy_dcline(1)); + s_to = nm.soln.z(ss.i1.legacy_dcline(2):ss.iN.legacy_dcline(2)); + vm_setpoint = abs(nme.C' * nm.soln.v); + + %% shadow prices on legacy DC line limits + vv = mm.get_idx(); + lambda = mm.soln.lambda; + mu_p_fr_lb = lambda.lower(vv.i1.Pdcf:vv.iN.Pdcf); + mu_p_fr_ub = lambda.upper(vv.i1.Pdcf:vv.iN.Pdcf); + mu_q_fr_lb = lambda.lower(vv.i1.Qdcf:vv.iN.Qdcf); + mu_q_fr_ub = lambda.upper(vv.i1.Qdcf:vv.iN.Qdcf); + mu_q_to_lb = lambda.lower(vv.i1.Qdct:vv.iN.Qdct); + mu_q_to_ub = lambda.upper(vv.i1.Qdct:vv.iN.Qdct); + + %% update in the data model + dme.tab.p_fr(dme.on) = real(s_fr) * dm.base_mva; + dme.tab.q_fr(dme.on) = -imag(s_fr) * dm.base_mva; + dme.tab.p_to(dme.on) = -real(s_to) * dm.base_mva; + dme.tab.q_to(dme.on) = -imag(s_to) * dm.base_mva; + dme.tab.vm_setpoint_fr(dme.on) = vm_setpoint(1:dme.n); + dme.tab.vm_setpoint_to(dme.on) = vm_setpoint(dme.n+1:end); + dme.tab.mu_p_fr_lb(dme.on) = mu_p_fr_lb / dm.base_mva; + dme.tab.mu_p_fr_ub(dme.on) = mu_p_fr_ub / dm.base_mva; + %% because of sign swap on reactive quantities, bounds are swapped + dme.tab.mu_q_fr_lb(dme.on) = mu_q_fr_ub / dm.base_mva; + dme.tab.mu_q_fr_ub(dme.on) = mu_q_fr_lb / dm.base_mva; + dme.tab.mu_q_to_lb(dme.on) = mu_q_to_ub / dm.base_mva; + dme.tab.mu_q_to_ub(dme.on) = mu_q_to_lb / dm.base_mva; + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/mme_legacy_dcline_opf_dc.m b/lib/t/+mp/mme_legacy_dcline_opf_dc.m new file mode 100644 index 00000000..9a425aee --- /dev/null +++ b/lib/t/+mp/mme_legacy_dcline_opf_dc.m @@ -0,0 +1,41 @@ +classdef mme_legacy_dcline_opf_dc < mp.mme_legacy_dcline_opf +% mp.mme_legacy_dcline_opf_dc - Math model element for legacy DC line for DC OPF. + +% MATPOWER +% Copyright (c) 2021-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + +% properties +% end %% properties + + methods + function obj = data_model_update_on(obj, mm, nm, dm, mpopt) + % + + dme = obj.data_model_element(dm); + + %% legacy DC line active power + ss = nm.get_idx('state'); + p_fr = nm.soln.z(ss.i1.legacy_dcline(1):ss.iN.legacy_dcline(1)); + p_to = nm.soln.z(ss.i1.legacy_dcline(2):ss.iN.legacy_dcline(2)); + + %% shadow prices on legacy DC line limits + vv = mm.get_idx(); + lambda = mm.soln.lambda; + mu_p_fr_lb = lambda.lower(vv.i1.Pdcf:vv.iN.Pdcf); + mu_p_fr_ub = lambda.upper(vv.i1.Pdcf:vv.iN.Pdcf); + + %% update in the data model + dme.tab.p_fr(dme.on) = p_fr * dm.base_mva; + dme.tab.q_fr(dme.on) = 0; + dme.tab.p_to(dme.on) = -p_to * dm.base_mva; + dme.tab.q_to(dme.on) = 0; + dme.tab.mu_p_fr_lb(dme.on) = mu_p_fr_lb / dm.base_mva; + dme.tab.mu_p_fr_ub(dme.on) = mu_p_fr_ub / dm.base_mva; + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/mme_legacy_dcline_pf_ac.m b/lib/t/+mp/mme_legacy_dcline_pf_ac.m new file mode 100644 index 00000000..da699a8b --- /dev/null +++ b/lib/t/+mp/mme_legacy_dcline_pf_ac.m @@ -0,0 +1,32 @@ +classdef mme_legacy_dcline_pf_ac < mp.mme_legacy_dcline +% mp.mme_legacy_dcline_pf_ac - Math model element for legacy DC line for AC power flow. + +% MATPOWER +% Copyright (c) 2022-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + +% properties +% end %% properties + + methods + function obj = data_model_update_on(obj, mm, nm, dm, mpopt) + % + + %% legacy DC line active power + ss = nm.get_idx('state'); + s_fr = nm.soln.z(ss.i1.legacy_dcline(1):ss.iN.legacy_dcline(1)); + s_to = nm.soln.z(ss.i1.legacy_dcline(2):ss.iN.legacy_dcline(2)); + + %% update in the data model + dme = obj.data_model_element(dm); + dme.tab.p_fr(dme.on) = real(s_fr) * dm.base_mva; + dme.tab.q_fr(dme.on) = -imag(s_fr) * dm.base_mva; + dme.tab.p_to(dme.on) = -real(s_to) * dm.base_mva; + dme.tab.q_to(dme.on) = -imag(s_to) * dm.base_mva; + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/mme_legacy_dcline_pf_dc.m b/lib/t/+mp/mme_legacy_dcline_pf_dc.m new file mode 100644 index 00000000..06ee10e8 --- /dev/null +++ b/lib/t/+mp/mme_legacy_dcline_pf_dc.m @@ -0,0 +1,32 @@ +classdef mme_legacy_dcline_pf_dc < mp.mme_legacy_dcline +% mp.mme_legacy_dcline_pf_dc - Math model element for legacy DC line for DC power flow. + +% MATPOWER +% Copyright (c) 2022-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + +% properties +% end %% properties + + methods + function obj = data_model_update_on(obj, mm, nm, dm, mpopt) + % + + %% legacy DC line active power + ss = nm.get_idx('state'); + p_fr = nm.soln.z(ss.i1.legacy_dcline(1):ss.iN.legacy_dcline(1)); + p_to = nm.soln.z(ss.i1.legacy_dcline(2):ss.iN.legacy_dcline(2)); + + %% update in the data model + dme = obj.data_model_element(dm); + dme.tab.p_fr(dme.on) = p_fr * dm.base_mva; + dme.tab.q_fr(dme.on) = 0; + dme.tab.p_to(dme.on) = -p_to * dm.base_mva; + dme.tab.q_to(dme.on) = 0; + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/nme_legacy_dcline.m b/lib/t/+mp/nme_legacy_dcline.m new file mode 100644 index 00000000..12f15e00 --- /dev/null +++ b/lib/t/+mp/nme_legacy_dcline.m @@ -0,0 +1,31 @@ +classdef (Abstract) nme_legacy_dcline < mp.nm_element +% mp.nme_legacy_dcline - Network model element abstract base class for legacy DC line. + +% MATPOWER +% Copyright (c) 2019-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + +% properties +% end %% properties + + methods + function name = name(obj) + % + name = 'legacy_dcline'; + end + + function np = np(obj) + % + np = 2; %% this is a 1 port element + end + + function nz = nz(obj) + % + nz = 2; %% 2 (possibly complex) non-voltage state per element + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/nme_legacy_dcline_ac.m b/lib/t/+mp/nme_legacy_dcline_ac.m new file mode 100644 index 00000000..e861e23f --- /dev/null +++ b/lib/t/+mp/nme_legacy_dcline_ac.m @@ -0,0 +1,33 @@ +classdef (Abstract) nme_legacy_dcline_ac < mp.nme_legacy_dcline & mp.form_ac +% mp.nme_legacy_dcline_ac - Network model element abstract base class for legacy DC line for AC formulation. + +% MATPOWER +% Copyright (c) 2019-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + + methods + function obj = add_zvars(obj, nm, dm, idx) + % + ndc = obj.nk; + dme = obj.data_model_element(dm); + switch idx{1} + case 1 % flow at "from" + nm.add_var('zr', 'Pdcf', ndc, dme.p_fr_start, dme.p_fr_lb, dme.p_fr_ub); + nm.add_var('zi', 'Qdcf', ndc, dme.q_fr_start, dme.q_fr_lb, dme.q_fr_ub); + case 2 % flow at "to" + nm.add_var('zr', 'Pdct', ndc, dme.p_to_start, -Inf, Inf); + nm.add_var('zi', 'Qdct', ndc, dme.q_to_start, dme.q_to_lb, dme.q_to_ub); + end + end + + function obj = build_params(obj, nm, dm) + % + build_params@mp.nme_legacy_dcline(obj, nm, dm); %% call parent + obj.N = speye(obj.nk * obj.nz); + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/nme_legacy_dcline_acc.m b/lib/t/+mp/nme_legacy_dcline_acc.m new file mode 100644 index 00000000..e660e5f5 --- /dev/null +++ b/lib/t/+mp/nme_legacy_dcline_acc.m @@ -0,0 +1,12 @@ +classdef nme_legacy_dcline_acc < mp.nme_legacy_dcline_ac & mp.form_acc +% mp.nme_legacy_dcline_acc - Network model element for legacy DC line for for AC cartesian voltage formulations. + +% MATPOWER +% Copyright (c) 2019-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + +end %% classdef diff --git a/lib/t/+mp/nme_legacy_dcline_acp.m b/lib/t/+mp/nme_legacy_dcline_acp.m new file mode 100644 index 00000000..52d3eab7 --- /dev/null +++ b/lib/t/+mp/nme_legacy_dcline_acp.m @@ -0,0 +1,12 @@ +classdef nme_legacy_dcline_acp < mp.nme_legacy_dcline_ac & mp.form_acp +% mp.nme_legacy_dcline_acp - Network model element for legacy DC line for for AC polar voltage formulations. + +% MATPOWER +% Copyright (c) 2019-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + +end %% classdef diff --git a/lib/t/+mp/nme_legacy_dcline_dc.m b/lib/t/+mp/nme_legacy_dcline_dc.m new file mode 100644 index 00000000..183c8572 --- /dev/null +++ b/lib/t/+mp/nme_legacy_dcline_dc.m @@ -0,0 +1,31 @@ +classdef nme_legacy_dcline_dc < mp.nme_legacy_dcline & mp.form_dc +% mp.nme_legacy_dcline_dc - Network model element for legacy DC line for DC formulation. + +% MATPOWER +% Copyright (c) 2019-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + + methods + function obj = add_zvars(obj, nm, dm, idx) + % + ndc = obj.nk; + dme = obj.data_model_element(dm); + switch idx{1} + case 1 % flow at "from" + nm.add_var('z', 'Pdcf', ndc, dme.p_fr_start, dme.p_fr_lb, dme.p_fr_ub); + case 2 % flow at "to" + nm.add_var('z', 'Pdct', ndc, dme.p_to_start, -Inf, Inf); + end + end + + function obj = build_params(obj, nm, dm) + % + build_params@mp.nme_legacy_dcline(obj, nm, dm); %% call parent + obj.K = speye(obj.nk * obj.nz); + end + end %% methods +end %% classdef diff --git a/lib/t/+mp/xt_legacy_dcline.m b/lib/t/+mp/xt_legacy_dcline.m new file mode 100644 index 00000000..e54462cc --- /dev/null +++ b/lib/t/+mp/xt_legacy_dcline.m @@ -0,0 +1,151 @@ +classdef xt_legacy_dcline < mp.extension +% mp.xt_legacy_dcline - |MATPOWER| extension to add legacy DC line elements. +% +% For AC power flow, continuation power flow, and optimial power flow problems, +% adds a new element type: +% +% - ``'legacy_dcline'`` - legacy DC line +% +% No changes are required for the task or container classes, so only the +% ``..._element_classes`` methods are overridden. +% +% The set of data model element classes depends on the task, with each OPF +% class inheriting from the corresponding class used for PF and CPF. +% +% The set of network model element classes depends on the formulation, +% specifically whether cartesian or polar representations are used for +% voltages. +% +% And the set of mathematical model element classes depends on both the task +% and the formulation. +% +% mp.xt_legacy_dcline Methods: +% * dmc_element_classes - add a class to data model converter elements +% * dm_element_classes - add a class to data model elements +% * nm_element_classes - add a class to network model elements +% * mm_element_classes - add a class to mathematical model elements +% +% See the :ref:`sec_customizing` and :ref:`sec_extensions` sections in the +% |MATPOWER-Dev-Manual| for more information, and specifically the +% :ref:`sec_element_classes` section and the :ref:`tab_element_class_modifiers` +% table for details on *element class modifiers*. +% +% See also mp.extension. + +% MATPOWER +% Copyright (c) 2022-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + +% properties +% end %% properties + + methods + function dmc_elements = dmc_element_classes(obj, dmc_class, fmt, mpopt) + % Add a class to data model converter elements. + % + % For ``'mpc2`` data formats, adds the classes: + % + % - mp.dmce_legacy_dcline_mpc2 + + switch fmt + case 'mpc2' + dmc_elements = { @mp.dmce_legacy_dcline_mpc2 }; + otherwise + dmc_elements = {}; + end + end + + function dm_elements = dm_element_classes(obj, dm_class, task_tag, mpopt) + % Add a class to data model elements. + % + % For ``'PF'`` and ``'CPF'`` tasks, adds the class: + % + % - mp.dme_legacy_dcline + % + % For ``'OPF'`` tasks, adds the class: + % + % - mp.dme_legacy_dcline_opf + + switch task_tag + case {'PF', 'CPF'} + dm_elements = { @mp.dme_legacy_dcline }; + case 'OPF' + dm_elements = { @mp.dme_legacy_dcline_opf }; + otherwise + dm_elements = {}; + end + end + + function nm_elements = nm_element_classes(obj, nm_class, task_tag, mpopt) + % Add a class to network model elements. + % + % For DC formulations, adds the class: + % + % - mp.nme_legacy_dcline_dc + % + % For AC *cartesian* voltage formulations, adds the class: + % + % - mp.nme_legacy_dcline_acc + % + % For AC *polar* voltage formulations, adds the class: + % + % - mp.nme_legacy_dcline_acp + + switch task_tag + case {'PF', 'CPF'} + v_cartesian = mpopt.pf.v_cartesian; + case {'OPF'} + v_cartesian = mpopt.opf.v_cartesian; + end + switch upper(mpopt.model) + case 'AC' + if v_cartesian + nm_elements = { @mp.nme_legacy_dcline_acc }; + else + nm_elements = { @mp.nme_legacy_dcline_acp }; + end + case 'DC' + nm_elements = { @mp.nme_legacy_dcline_dc }; + otherwise + nm_elements = {}; + end + end + + function mm_elements = mm_element_classes(obj, mm_class, task_tag, mpopt) + % Add a class to mathematical model elements. + % + % For ``'PF'`` and ``'CPF'`` tasks, adds the class: + % + % - mp.mme_legacy_dcline_pf_dc *(DC formulation)* or + % - mp.mme_legacy_dcline_pf_ac *(AC formulation)* + % + % For ``'OPF'`` tasks, adds the class: + % + % - mp.mme_legacy_dcline_opf_dc *(DC formulation)* or + % - mp.mme_legacy_dcline_opf_ac *(AC formulation)* + + switch task_tag + case {'PF', 'CPF'} + switch upper(mpopt.model) + case 'AC' + mm_elements = { @mp.mme_legacy_dcline_pf_ac }; + case 'DC' + mm_elements = { @mp.mme_legacy_dcline_pf_dc }; + end + case {'OPF'} + switch upper(mpopt.model) + case 'AC' + mm_elements = { @mp.mme_legacy_dcline_opf_ac }; + case 'DC' + mm_elements = { @mp.mme_legacy_dcline_opf_dc }; + end + otherwise + dm_elements = {}; + end + end + end %% methods +end %% classdef diff --git a/lib/t/t_dcline.m b/lib/t/t_dcline.m index b4e295dd..f1a0609c 100644 --- a/lib/t/t_dcline.m +++ b/lib/t/t_dcline.m @@ -2,7 +2,7 @@ function t_dcline(quiet) %T_DCLINE Tests for DC line extension in TOGGLE_DCLINE. % MATPOWER -% Copyright (c) 2011-2016, Power Systems Engineering Research Center (PSERC) +% Copyright (c) 2011-2024, Power Systems Engineering Research Center (PSERC) % by Ray Zimmerman, PSERC Cornell % % This file is part of MATPOWER. @@ -13,7 +13,7 @@ function t_dcline(quiet) quiet = 0; end -num_tests = 63; +num_tests = 62; t_begin(num_tests, quiet); @@ -95,7 +95,7 @@ function t_dcline(quiet) t_is(r.branch(:,ibr_mu ), r0.branch(:,ibr_mu ), 2, [t 'branch mu']); t = 'AC PF (no DC lines) : '; -mpc1 = struct('baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); [mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); @@ -128,7 +128,7 @@ function t_dcline(quiet) t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), expected, 3, [t 'mu']); t = 'AC PF (with DC lines) : '; -mpc1 = struct('baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); [mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); @@ -138,15 +138,14 @@ function t_dcline(quiet) [rp, success] = runpf(mpc1, mpopt); t_ok(success, [t 'success']); t_is( rp.bus(:,ib_voltage), r.bus(:,ib_voltage), 3, [t 'bus voltage']); -%t_is( rp.gen(:,ig_disp ), r.gen(:,ig_disp ), 3, [t 'gen dispatch']); -t_is( rp.gen([1;3],ig_disp ), r.gen([1;3],ig_disp ), 3, [t 'gen dispatch']); -t_is( rp.gen(2,PG ), r.gen(2,PG ), 3, [t 'gen dispatch']); t_is( rp.gen(2,QG)+rp.dcline(1,c.QF), r.gen(2,QG)+r.dcline(1,c.QF), 3, [t 'gen dispatch']); +rp.gen(2, QG) = r.gen(2, QG); +t_is( rp.gen(:,ig_disp ), r.gen(:,ig_disp ), 3, [t 'gen dispatch']); t_is(rp.branch(:,ibr_flow ), r.branch(:,ibr_flow ), 3, [t 'branch flow']); %% add appropriate P and Q injections and check angles and generation when running PF t = 'AC PF (with equivalent injections) : '; -mpc1 = struct('baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); [mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); @@ -196,7 +195,7 @@ function t_dcline(quiet) t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), expected, 3, [t 'mu']); t = 'DC PF (with DC lines) : '; -mpc1 = struct('baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); [mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); @@ -210,7 +209,7 @@ function t_dcline(quiet) %% add appropriate P injections and check angles and generation when running PF t = 'DC PF (with equivalent injections) : '; -mpc1 = struct('baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); [mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); diff --git a/lib/t/t_mpxt_legacy_dcline.m b/lib/t/t_mpxt_legacy_dcline.m new file mode 100644 index 00000000..a3301def --- /dev/null +++ b/lib/t/t_mpxt_legacy_dcline.m @@ -0,0 +1,330 @@ +function t_mpxt_legacy_dcline(quiet) +%T_MPXT_LEGACY_DCLINE Tests for legacy DC line extension in MP.XT_LEGACY_DCLINE. + +% MATPOWER +% Copyright (c) 2011-2024, Power Systems Engineering Research Center (PSERC) +% by Ray Zimmerman, PSERC Cornell +% +% This file is part of MATPOWER. +% Covered by the 3-clause BSD License (see LICENSE file for details). +% See https://matpower.org for more info. + +if nargin < 1 + quiet = 0; +end + +num_tests = 62; + +t_begin(num_tests, quiet); + +%% define named indices into data matrices +[PQ, PV, REF, NONE, BUS_I, BUS_TYPE, PD, QD, GS, BS, BUS_AREA, VM, ... + VA, BASE_KV, ZONE, VMAX, VMIN, LAM_P, LAM_Q, MU_VMAX, MU_VMIN] = idx_bus; +[GEN_BUS, PG, QG, QMAX, QMIN, VG, MBASE, GEN_STATUS, PMAX, PMIN, ... + MU_PMAX, MU_PMIN, MU_QMAX, MU_QMIN, PC1, PC2, QC1MIN, QC1MAX, ... + QC2MIN, QC2MAX, RAMP_AGC, RAMP_10, RAMP_30, RAMP_Q, APF] = idx_gen; +[F_BUS, T_BUS, BR_R, BR_X, BR_B, RATE_A, RATE_B, RATE_C, ... + TAP, SHIFT, BR_STATUS, PF, QF, PT, QT, MU_SF, MU_ST, ... + ANGMIN, ANGMAX, MU_ANGMIN, MU_ANGMAX] = idx_brch; +[PW_LINEAR, POLYNOMIAL, MODEL, STARTUP, SHUTDOWN, NCOST, COST] = idx_cost; +c = idx_dcline; + +casefile = 't_case9_dcline'; +casefile2 = 'case9'; +if quiet + verbose = 0; +else + verbose = 0; +end +if have_feature('octave') + if have_feature('octave', 'vnum') >= 4 + file_in_path_warn_id = 'Octave:data-file-in-path'; + else + file_in_path_warn_id = 'Octave:load-file-in-path'; + end + s1 = warning('query', file_in_path_warn_id); + warning('off', file_in_path_warn_id); +else + sing_matrix_warn_id = 'MATLAB:nearlySingularMatrix'; + s2 = warning('query', sing_matrix_warn_id); + warning('off', sing_matrix_warn_id); +end + +mpopt = mpoption('opf.violation', 1e-6, 'mips.gradtol', 1e-8, ... + 'mips.comptol', 1e-8, 'mips.costtol', 1e-9); +mpopt = mpoption(mpopt, 'opf.ac.solver', 'MIPS', 'opf.dc.solver', 'MIPS'); +mpopt = mpoption(mpopt, 'out.all', 0, 'verbose', verbose); + +%% set up indices +ib_data = [1:BUS_AREA BASE_KV:VMIN]; +ib_voltage = [VM VA]; +ib_lam = [LAM_P LAM_Q]; +ib_mu = [MU_VMAX MU_VMIN]; +ig_data = [GEN_BUS QMAX QMIN MBASE:APF]; +ig_disp = [PG QG VG]; +ig_mu = (MU_PMAX:MU_QMIN); +ibr_data = (1:ANGMAX); +ibr_flow = (PF:QT); +ibr_mu = [MU_SF MU_ST]; +ibr_angmu = [MU_ANGMIN MU_ANGMAX]; + +%% load case +mpc0 = loadcase(casefile); +mpc0 = rmfield(mpc0, 'dclinecost'); +mpc = mpc0; +ndc = size(mpc.dcline, 1); + +%% run AC OPF w/o DC lines +t = 'AC OPF (no DC lines) : '; +[r0, success] = runopf(mpc0, mpopt); +t_ok(success, [t 'success']); +task = run_opf(mpc, mpopt); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is(task.mm.soln.f, r0.f, 8, [t 'f']); +t_is( r.bus(:,ib_data ), r0.bus(:,ib_data ), 10, [t 'bus data']); +t_is( r.bus(:,ib_voltage), r0.bus(:,ib_voltage), 3, [t 'bus voltage']); +t_is( r.bus(:,ib_lam ), r0.bus(:,ib_lam ), 3, [t 'bus lambda']); +t_is( r.bus(:,ib_mu ), r0.bus(:,ib_mu ), 2, [t 'bus mu']); +t_is( r.gen(:,ig_data ), r0.gen(:,ig_data ), 10, [t 'gen data']); +t_is( r.gen(:,ig_disp ), r0.gen(:,ig_disp ), 3, [t 'gen dispatch']); +t_is( r.gen(:,ig_mu ), r0.gen(:,ig_mu ), 3, [t 'gen mu']); +t_is(r.branch(:,ibr_data ), r0.branch(:,ibr_data ), 10, [t 'branch data']); +t_is(r.branch(:,ibr_flow ), r0.branch(:,ibr_flow ), 3, [t 'branch flow']); +t_is(r.branch(:,ibr_mu ), r0.branch(:,ibr_mu ), 2, [t 'branch mu']); + +t = 'AC PF (no DC lines) : '; +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +[mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... + deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... + r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); +mpc1.bus(:, VM) = 1; +mpc1.bus(:, VA) = 0; +task = run_pf(mpc1, mpopt); +rp = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is( rp.bus(:,ib_voltage), r.bus(:,ib_voltage), 3, [t 'bus voltage']); +t_is( rp.gen(:,ig_disp ), r.gen(:,ig_disp ), 3, [t 'gen dispatch']); +t_is(rp.branch(:,ibr_flow ), r.branch(:,ibr_flow ), 3, [t 'branch flow']); + +%% run with DC lines +t = 'AC OPF (with DC lines) : '; +task = run_opf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +expected = [ + 10 8.9 -10 10 1.0674 1.0935; + 2.2776 2.2776 0 0 1.0818 1.0665; + 0 0 0 0 1.0000 1.0000; + 10 9.5 0.0563 -10 1.0778 1.0665; +]; +t_is(r.dcline(:, c.PF:c.VT), expected, 4, [t 'P Q V']); +expected = [ + 0 0.8490 0.6165 0 0 0.2938; + 0 0 0 0.4290 0.0739 0; + 0 0 0 0 0 0; + 0 7.2209 0 0 0.0739 0; +]; +t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), expected, 3, [t 'mu']); + +t = 'AC PF (with DC lines) : '; +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +[mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... + deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... + r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); +mpc1.bus(:, VM) = 1; +mpc1.bus(:, VA) = 0; +task = run_pf(mpc1, mpopt, 'mpx', mp.xt_legacy_dcline); +rp = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is( rp.bus(:,ib_voltage), r.bus(:,ib_voltage), 3, [t 'bus voltage']); +t_is( rp.gen(2,QG)+rp.dcline(1,c.QF), r.gen(2,QG)+r.dcline(1,c.QF), 3, [t 'gen dispatch']); +rp.gen(2, QG) = r.gen(2, QG); +t_is( rp.gen(:,ig_disp ), r.gen(:,ig_disp ), 3, [t 'gen dispatch']); +t_is(rp.branch(:,ibr_flow ), r.branch(:,ibr_flow ), 3, [t 'branch flow']); + +%% add appropriate P and Q injections and check angles and generation when running PF +t = 'AC PF (with equivalent injections) : '; +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +[mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... + deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... + r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); +mpc1.bus(:, VM) = 1; +mpc1.bus(:, VA) = 0; +for k = 1:ndc + if mpc1.dcline(k, c.BR_STATUS) + ff = find(mpc1.bus(:, BUS_I) == mpc1.dcline(k, c.F_BUS)); + tt = find(mpc1.bus(:, BUS_I) == mpc1.dcline(k, c.T_BUS)); + mpc1.bus(ff, PD) = mpc1.bus(ff, PD) + r.dcline(k, c.PF); + mpc1.bus(ff, QD) = mpc1.bus(ff, QD) - r.dcline(k, c.QF); + mpc1.bus(tt, PD) = mpc1.bus(tt, PD) - r.dcline(k, c.PT); + mpc1.bus(tt, QD) = mpc1.bus(tt, QD) - r.dcline(k, c.QT); + mpc1.bus(ff, VM) = r.dcline(k, c.VF); + mpc1.bus(tt, VM) = r.dcline(k, c.VT); + mpc1.bus(ff, BUS_TYPE) = PV; + mpc1.bus(tt, BUS_TYPE) = PV; + end +end +task = run_pf(mpc1, mpopt); +rp = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is( rp.bus(:,ib_voltage), r.bus(:,ib_voltage), 3, [t 'bus voltage']); +t_is( rp.gen(:,ig_disp ), r.gen(:,ig_disp ), 3, [t 'gen dispatch']); +t_is(rp.branch(:,ibr_flow ), r.branch(:,ibr_flow ), 3, [t 'branch flow']); + +%% test DC OPF +t = 'DC OPF (with DC lines) : '; +mpc = mpc0; +mpc.gen(1, PMIN) = 10; +mpc.branch(5, RATE_A) = 100; +mpopt = mpoption(mpopt, 'model', 'DC'); +task = run_opf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +expected = [ + 10 8.9 0 0 1.01 1; + 2 2 0 0 1 1; + 0 0 0 0 1 1; + 10 9.5 0 0 1 0.98; +]; +t_is(r.dcline(:, c.PF:c.VT), expected, 4, [t 'P Q V']); +expected = [ + 0 1.8602 0 0 0 0; + 1.8507 0 0 0 0 0; + 0 0 0 0 0 0; + 0 0.2681 0 0 0 0; +]; +t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), expected, 3, [t 'mu']); + +t = 'DC PF (with DC lines) : '; +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +[mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... + deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... + r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); +mpc1.bus(:, VA) = 0; +task = run_pf(mpc1, mpopt, 'mpx', mp.xt_legacy_dcline); +rp = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is( rp.bus(:,ib_voltage), r.bus(:,ib_voltage), 3, [t 'bus voltage']); +t_is( rp.gen(:,ig_disp ), r.gen(:,ig_disp ), 3, [t 'gen dispatch']); +t_is(rp.branch(:,ibr_flow ), r.branch(:,ibr_flow ), 3, [t 'branch flow']); + +%% add appropriate P injections and check angles and generation when running PF +t = 'DC PF (with equivalent injections) : '; +mpc1 = struct('version', '2', 'baseMVA', [], 'bus', [], 'branch', [], 'gencost', [], 'dcline', []); +[mpc1.baseMVA, mpc1.bus, mpc1.gen, mpc1.branch, mpc1.gencost, mpc1.dcline] = ... + deal(r.baseMVA, r.bus(:, 1:VMIN), r.gen(:, 1:APF), ... + r.branch(:, 1:ANGMAX), r.gencost, r.dcline(:, 1:c.LOSS1)); +mpc1.bus(:, VA) = 0; +for k = 1:ndc + if mpc1.dcline(k, c.BR_STATUS) + ff = find(mpc1.bus(:, BUS_I) == mpc1.dcline(k, c.F_BUS)); + tt = find(mpc1.bus(:, BUS_I) == mpc1.dcline(k, c.T_BUS)); + mpc1.bus(ff, PD) = mpc1.bus(ff, PD) + r.dcline(k, c.PF); + mpc1.bus(tt, PD) = mpc1.bus(tt, PD) - r.dcline(k, c.PT); + mpc1.bus(ff, BUS_TYPE) = PV; + mpc1.bus(tt, BUS_TYPE) = PV; + end +end +task = run_pf(mpc1, mpopt); +rp = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is( rp.bus(:,ib_voltage), r.bus(:,ib_voltage), 3, [t 'bus voltage']); +t_is( rp.gen(:,ig_disp ), r.gen(:,ig_disp ), 3, [t 'gen dispatch']); +t_is(rp.branch(:,ibr_flow ), r.branch(:,ibr_flow ), 3, [t 'branch flow']); + +%% run with DC lines +t = 'AC OPF (with DC lines + poly cost) : '; +mpc = loadcase(casefile); +mpopt = mpoption(mpopt, 'model', 'AC'); +task = run_opf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +expected1 = [ + 10 8.9 -10 10 1.0663 1.0936; + 7.8429 7.8429 0 0 1.0809 1.0667; + 0 0 0 0 1.0000 1.0000; + 6.0549 5.7522 -0.5897 -10 1.0778 1.0667; +]; +t_is(r.dcline(:, c.PF:c.VT), expected1, 4, [t 'P Q V']); +expected2 = [ + 0 0.7605 0.6226 0 0 0.2980; + 0 0 0 0.4275 0.0792 0; + 0 0 0 0 0 0; + 0 0 0 0 0.0792 0; +]; +t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), expected2, 3, [t 'mu']); + +mpc.dclinecost(4, 1:8) = [2 0 0 4 0 0 7.3 0]; +task = run_opf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is(r.dcline(:, c.PF:c.VT), expected1, 4, [t 'P Q V']); +t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), expected2, 3, [t 'mu']); + +t = 'AC OPF (with DC lines + pwl cost) : '; +mpc.dclinecost(4, 1:8) = [1 0 0 2 0 0 10 73]; +task = run_opf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is(r.dcline(:, c.PF:c.VT), expected1, 4, [t 'P Q V']); +t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), expected2, 3, [t 'mu']); + +t = 'AC OPF w/Qg cost (with DC lines + pwl cost) : '; +mpc.gencost = [mpc.gencost; mpc.gencost]; +mpc.gencost(4:end, COST:end) = 0; +mpc.gencost(4:end, MODEL) = 2; +mpc.gencost(4:end, NCOST) = 2; +mpc.gencost(5, COST+1) = 1; +task = run_opf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is(r.dcline(:, c.PF:c.VT), expected1, 4, [t 'P Q V']); +t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), expected2, 3, [t 'mu']); + +t = 'AC OPF (isolated gen bus w/DC connection) : '; +mpc = loadcase(casefile2); +% use case9 with bus 2 connected by DC line instead of AC line +% requires making bus 2 a REF bus as well +%%----- DC Line Data ----- +% fbus tbus status Pf Pt Qf Qt Vf Vt Pmin Pmax QminF QmaxF QminT QmaxT loss0 loss1 +mpc.dcline = [ + 8 2 1 -163 163 0 0 1.025 1.025 -200 200 0 0 0 0 0 0; +]; +mpc.branch(7, BR_STATUS) = 0; +mpc.bus(2, BUS_TYPE) = REF; + +task = run_opf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is(r.dcline(:, c.PF:c.VT), [-134.3323 -134.3323 0 0 1.1 1], 4, [t 'P Q V']); +t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), [0 0 0.1311 0 0 0], 4, [t 'mu']); + +t = 'AC PF (isolated gen bus w/DC connection) : '; +task = run_pf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is(r.dcline(:, c.PF:c.VT), [-163 -163 -9.6926 0 1.025 1.025], 4, [t 'P Q V']); + +t = 'DC OPF (isolated gen bus w/DC connection) : '; +mpopt = mpoption(mpopt, 'model', 'DC'); +task = run_opf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is(r.dcline(:, c.PF:c.VT), [-134.3776 -134.3776 0 0 1.025 1.025], 4, [t 'P Q V']); +t_is(r.dcline(:, c.MU_PMIN:c.MU_QMAXT), [0 0 0 0 0 0], 3, [t 'mu']); + +t = 'DC PF (isolated gen bus w/DC connection) : '; +task = run_pf(mpc, mpopt, 'mpx', mp.xt_legacy_dcline); +r = task.dmc.export(task.dm, task.dm.source); +t_ok(task.success, [t 'success']); +t_is(r.dcline(:, c.PF:c.VT), [-163 -163 0 0 1.025 1.025], 4, [t 'P Q V']); + + +if have_feature('octave') + warning(s1.state, file_in_path_warn_id); +else + warning(s2.state, sing_matrix_warn_id); +end + +t_end; diff --git a/lib/t/test_matpower.m b/lib/t/test_matpower.m index 4b919a33..e8c82f71 100644 --- a/lib/t/test_matpower.m +++ b/lib/t/test_matpower.m @@ -89,6 +89,7 @@ tests{end+1} = 't_run_mp_3p'; tests{end+1} = 't_pretty_print'; tests{end+1} = 't_mpxt_reserves'; + tests{end+1} = 't_mpxt_legacy_dcline'; end %% more MATPOWER