diff --git a/schema/column_conventions.json b/schema/column_conventions.json index a4872b7c..780d5d1a 100644 --- a/schema/column_conventions.json +++ b/schema/column_conventions.json @@ -67,6 +67,10 @@ {"table": "transformer_circuits", "column": "base_power", "quantity_kind": "ApparentPower", "unit": "MVA", "description": "Base power the row's per-unit values resolve against"}, {"table": "transformer_circuits", "column": "base_voltage_primary", "quantity_kind": "Voltage", "unit": "kV", "description": "Primary (from) terminal-side base voltage; reference for the circuit's pu impedance"}, {"table": "transformer_circuits", "column": "base_voltage_secondary", "quantity_kind": "Voltage", "unit": "kV", "description": "Secondary (to) terminal-side base voltage"}, + {"table": "transformer_circuits", "column": "load_drop_compensation_r", "quantity_kind": "Resistance", "unit": "pu", "discriminator_column": "parameter_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "base_voltage_ref": "base_voltage_primary", "description": "Resistive part of the load drop compensation impedance (PSS/E CR), per-unit on the row's base_power, referenced to base_voltage_primary"}, + {"table": "transformer_circuits", "column": "load_drop_compensation_r", "quantity_kind": "Resistance", "unit": "ohm", "discriminator_column": "parameter_units", "discriminator_value": "NATURAL_UNITS", "description": "Resistive part of the load drop compensation impedance (PSS/E CR), in ohm"}, + {"table": "transformer_circuits", "column": "load_drop_compensation_x", "quantity_kind": "Reactance", "unit": "pu", "discriminator_column": "parameter_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "base_voltage_ref": "base_voltage_primary", "description": "Reactive part of the load drop compensation impedance (PSS/E CX), per-unit on the row's base_power, referenced to base_voltage_primary"}, + {"table": "transformer_circuits", "column": "load_drop_compensation_x", "quantity_kind": "Reactance", "unit": "ohm", "discriminator_column": "parameter_units", "discriminator_value": "NATURAL_UNITS", "description": "Reactive part of the load drop compensation impedance (PSS/E CX), in ohm"}, {"table": "two_winding_transformers", "column": "magnetizing_shunt.real", "quantity_kind": "Conductance", "unit": "pu", "base_power_ref": "circuit->transformer_circuits.base_power", "base_voltage_ref": "circuit->transformer_circuits.base_voltage_primary", "description": "Magnetizing shunt conductance, per-unit on the circuit's base_power (magnetizing_shunt JSON path)"}, {"table": "two_winding_transformers", "column": "magnetizing_shunt.imag", "quantity_kind": "Susceptance", "unit": "pu", "base_power_ref": "circuit->transformer_circuits.base_power", "base_voltage_ref": "circuit->transformer_circuits.base_voltage_primary", "description": "Magnetizing shunt susceptance, per-unit on the circuit's base_power (magnetizing_shunt JSON path)"}, {"table": "three_winding_transformers", "column": "r_12", "quantity_kind": "Resistance", "unit": "pu", "discriminator_column": "parameter_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power_12", "base_voltage_ref": "primary_circuit->transformer_circuits.base_voltage_primary", "description": "Measured primary-secondary resistance, per-unit on base_power_12, referred to the primary winding's voltage base per PSS/E convention"}, @@ -108,6 +112,8 @@ {"table": "thermal_generators", "column": "production_cost", "quantity_kind": "CostPerEnergy", "unit": "USD/MWh", "discriminator_column": "production_cost.value_curve.curve_type", "discriminator_value": "AVERAGE_RATE", "discriminator_column_2": "production_cost.variable_cost_type", "discriminator_value_2": "COST", "description": "Average-rate cost curve (production_cost column); payload power_units must be NATURAL_UNITS"}, {"table": "thermal_generators", "column": "production_cost", "quantity_kind": "HeatRate", "unit": "MMBtu/MWh", "discriminator_column": "production_cost.value_curve.curve_type", "discriminator_value": "INCREMENTAL", "discriminator_column_2": "production_cost.variable_cost_type", "discriminator_value_2": "FUEL", "description": "Incremental heat rate of a FuelCurve; the money is carried separately by fuel_cost (production_cost column)"}, {"table": "thermal_generators", "column": "production_cost", "quantity_kind": "HeatRate", "unit": "MMBtu/MWh", "discriminator_column": "production_cost.value_curve.curve_type", "discriminator_value": "AVERAGE_RATE", "discriminator_column_2": "production_cost.variable_cost_type", "discriminator_value_2": "FUEL", "description": "Average heat rate of a FuelCurve; the money is carried separately by fuel_cost (production_cost column)"}, + {"table": "thermal_generators", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "COMPONENT_BASE", "base_voltage_ref": "remote_regulated_bus_id->balancing_topologies.base_voltage", "description": "Voltage held at the regulated bus, per-unit on that bus's base voltage; the regulated bus is remote_regulated_bus_id when set, else balancing_topology"}, + {"table": "thermal_generators", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "NATURAL_UNITS", "description": "Voltage held at the regulated bus in kV"}, {"table": "renewable_generators", "column": "rating", "quantity_kind": "ApparentPower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "Nameplate rating, per-unit on the row's base_power"}, {"table": "renewable_generators", "column": "rating", "quantity_kind": "ApparentPower", "unit": "MVA", "discriminator_column": "power_units", "discriminator_value": "NATURAL_UNITS", "description": "Nameplate rating in MVA"}, {"table": "renewable_generators", "column": "base_power", "quantity_kind": "ApparentPower", "unit": "MVA", "description": "Base power the row's per-unit values resolve against"}, @@ -125,6 +131,8 @@ {"table": "renewable_generators", "column": "operation_cost.curtailment_cost", "quantity_kind": "CostPerTime", "unit": "USD/h", "discriminator_column": "operation_cost.curtailment_cost.value_curve.curve_type", "discriminator_value": "INPUT_OUTPUT", "description": "Curtailment input-output cost curve: y is a cost rate at a power level (operation_cost JSON path); payload power_units must be NATURAL_UNITS"}, {"table": "renewable_generators", "column": "operation_cost.curtailment_cost", "quantity_kind": "CostPerEnergy", "unit": "USD/MWh", "discriminator_column": "operation_cost.curtailment_cost.value_curve.curve_type", "discriminator_value": "INCREMENTAL", "description": "Curtailment incremental (marginal) cost curve (operation_cost JSON path); payload power_units must be NATURAL_UNITS"}, {"table": "renewable_generators", "column": "operation_cost.curtailment_cost", "quantity_kind": "CostPerEnergy", "unit": "USD/MWh", "discriminator_column": "operation_cost.curtailment_cost.value_curve.curve_type", "discriminator_value": "AVERAGE_RATE", "description": "Curtailment average-rate cost curve (operation_cost JSON path); payload power_units must be NATURAL_UNITS"}, + {"table": "renewable_generators", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "COMPONENT_BASE", "base_voltage_ref": "remote_regulated_bus_id->balancing_topologies.base_voltage", "description": "Voltage held at the regulated bus, per-unit on that bus's base voltage; the regulated bus is remote_regulated_bus_id when set, else balancing_topology"}, + {"table": "renewable_generators", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "NATURAL_UNITS", "description": "Voltage held at the regulated bus in kV"}, {"table": "hydro_generators", "column": "rating", "quantity_kind": "ApparentPower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "Nameplate rating, per-unit on the row's base_power"}, {"table": "hydro_generators", "column": "rating", "quantity_kind": "ApparentPower", "unit": "MVA", "discriminator_column": "power_units", "discriminator_value": "NATURAL_UNITS", "description": "Nameplate rating in MVA"}, {"table": "hydro_generators", "column": "base_power", "quantity_kind": "ApparentPower", "unit": "MVA", "description": "Base power the row's per-unit values resolve against"}, @@ -149,6 +157,8 @@ {"table": "hydro_generators", "column": "production_cost", "quantity_kind": "CostPerEnergy", "unit": "USD/MWh", "discriminator_column": "production_cost.value_curve.curve_type", "discriminator_value": "AVERAGE_RATE", "discriminator_column_2": "production_cost.variable_cost_type", "discriminator_value_2": "COST", "description": "Average-rate cost curve (production_cost column); payload power_units must be NATURAL_UNITS"}, {"table": "hydro_generators", "column": "production_cost", "quantity_kind": "HeatRate", "unit": "MMBtu/MWh", "discriminator_column": "production_cost.value_curve.curve_type", "discriminator_value": "INCREMENTAL", "discriminator_column_2": "production_cost.variable_cost_type", "discriminator_value_2": "FUEL", "description": "Incremental heat rate of a FuelCurve; the money is carried separately by fuel_cost (production_cost column)"}, {"table": "hydro_generators", "column": "production_cost", "quantity_kind": "HeatRate", "unit": "MMBtu/MWh", "discriminator_column": "production_cost.value_curve.curve_type", "discriminator_value": "AVERAGE_RATE", "discriminator_column_2": "production_cost.variable_cost_type", "discriminator_value_2": "FUEL", "description": "Average heat rate of a FuelCurve; the money is carried separately by fuel_cost (production_cost column)"}, + {"table": "hydro_generators", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "COMPONENT_BASE", "base_voltage_ref": "remote_regulated_bus_id->balancing_topologies.base_voltage", "description": "Voltage held at the regulated bus, per-unit on that bus's base voltage; the regulated bus is remote_regulated_bus_id when set, else balancing_topology"}, + {"table": "hydro_generators", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "NATURAL_UNITS", "description": "Voltage held at the regulated bus in kV"}, {"table": "storage_units", "column": "rating", "quantity_kind": "ApparentPower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "Nameplate rating, per-unit on the row's base_power"}, {"table": "storage_units", "column": "rating", "quantity_kind": "ApparentPower", "unit": "MVA", "discriminator_column": "power_units", "discriminator_value": "NATURAL_UNITS", "description": "Nameplate rating in MVA"}, {"table": "storage_units", "column": "base_power", "quantity_kind": "ApparentPower", "unit": "MVA", "description": "Base power the row's per-unit values resolve against"}, @@ -184,6 +194,8 @@ {"table": "storage_units", "column": "operation_cost.discharge_variable_cost", "quantity_kind": "CostPerTime", "unit": "USD/h", "discriminator_column": "operation_cost.discharge_variable_cost.value_curve.curve_type", "discriminator_value": "INPUT_OUTPUT", "description": "Discharge input-output cost curve: y is a cost rate at a power level (operation_cost JSON path); payload power_units must be NATURAL_UNITS"}, {"table": "storage_units", "column": "operation_cost.discharge_variable_cost", "quantity_kind": "CostPerEnergy", "unit": "USD/MWh", "discriminator_column": "operation_cost.discharge_variable_cost.value_curve.curve_type", "discriminator_value": "INCREMENTAL", "description": "Discharge incremental (marginal) cost curve (operation_cost JSON path); payload power_units must be NATURAL_UNITS"}, {"table": "storage_units", "column": "operation_cost.discharge_variable_cost", "quantity_kind": "CostPerEnergy", "unit": "USD/MWh", "discriminator_column": "operation_cost.discharge_variable_cost.value_curve.curve_type", "discriminator_value": "AVERAGE_RATE", "description": "Discharge average-rate cost curve (operation_cost JSON path); payload power_units must be NATURAL_UNITS"}, + {"table": "storage_units", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "COMPONENT_BASE", "base_voltage_ref": "remote_regulated_bus_id->balancing_topologies.base_voltage", "description": "Voltage held at the regulated bus, per-unit on that bus's base voltage; the regulated bus is remote_regulated_bus_id when set, else balancing_topology"}, + {"table": "storage_units", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "NATURAL_UNITS", "description": "Voltage held at the regulated bus in kV"}, {"table": "hydro_reservoirs", "column": "storage_level_limits", "quantity_kind": "Volume", "unit": "m3", "discriminator_column": "level_data_type", "discriminator_value": "USABLE_VOLUME", "description": "JSON {min, max}"}, {"table": "hydro_reservoirs", "column": "storage_level_limits", "quantity_kind": "Volume", "unit": "m3", "discriminator_column": "level_data_type", "discriminator_value": "TOTAL_VOLUME", "description": "JSON {min, max}"}, {"table": "hydro_reservoirs", "column": "storage_level_limits", "quantity_kind": "Elevation", "unit": "m", "discriminator_column": "level_data_type", "discriminator_value": "HEAD", "description": "JSON {min, max}"}, @@ -218,14 +230,13 @@ {"table": "fixed_admittance", "column": "y_g", "quantity_kind": "ActivePower", "unit": "MW", "discriminator_column": "admittance_units", "discriminator_value": "COMPONENT_MVAR", "description": "Shunt conductance in the PSS/E form: MW at 1.0 pu voltage"}, {"table": "fixed_admittance", "column": "y_b", "quantity_kind": "Susceptance", "unit": "S", "discriminator_column": "admittance_units", "discriminator_value": "NATURAL_UNITS", "description": "Shunt susceptance in siemens"}, {"table": "fixed_admittance", "column": "y_b", "quantity_kind": "ReactivePower", "unit": "MVAr", "discriminator_column": "admittance_units", "discriminator_value": "COMPONENT_MVAR", "description": "Shunt susceptance in the PSS/E form: MVAr at 1.0 pu voltage"}, - {"table": "switched_admittance", "column": "Y_increase", "quantity_kind": "Susceptance", "unit": "S", "discriminator_column": "admittance_units", "discriminator_value": "NATURAL_UNITS", "description": "Per-step admittance increment in siemens"}, + {"table": "switched_admittance", "column": "Y_increase", "quantity_kind": "Admittance", "unit": "S", "discriminator_column": "admittance_units", "discriminator_value": "NATURAL_UNITS", "description": "Per-step admittance increment in siemens"}, {"table": "switched_admittance", "column": "Y_increase", "quantity_kind": "ReactivePower", "unit": "MVAr", "discriminator_column": "admittance_units", "discriminator_value": "COMPONENT_MVAR", "description": "Per-step admittance increment in the PSS/E form: MVAr at 1.0 pu voltage"}, {"table": "switched_admittance", "column": "solved_admittance", "quantity_kind": "Susceptance", "unit": "S", "discriminator_column": "admittance_units", "discriminator_value": "NATURAL_UNITS", "description": "Solved-case admittance (PSS/E BINIT) in siemens"}, {"table": "switched_admittance", "column": "solved_admittance", "quantity_kind": "ReactivePower", "unit": "MVAr", "discriminator_column": "admittance_units", "discriminator_value": "COMPONENT_MVAR", "description": "Solved-case admittance (PSS/E BINIT) in the PSS/E form: MVAr at 1.0 pu voltage"}, {"table": "switched_admittance", "column": "admittance_limits", "quantity_kind": "Susceptance", "unit": "S", "discriminator_column": "admittance_units", "discriminator_value": "NATURAL_UNITS", "description": "Admittance limits (JSON min/max) in siemens"}, {"table": "switched_admittance", "column": "admittance_limits", "quantity_kind": "ReactivePower", "unit": "MVAr", "discriminator_column": "admittance_units", "discriminator_value": "COMPONENT_MVAR", "description": "Admittance limits (JSON min/max) in the PSS/E form: MVAr at 1.0 pu voltage"}, {"table": "switched_admittance", "column": "control_mode", "quantity_kind": "Fraction", "unit": "1", "description": "Switched-shunt control mode"}, - {"table": "switched_admittance", "column": "regulated_bus_number", "quantity_kind": "Fraction", "unit": "1", "description": "Bus whose voltage/quantity this shunt regulates; 0 = local bus"}, {"table": "synchronous_condensers", "column": "reactive_power", "quantity_kind": "ReactivePower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "Initial reactive power setpoint, per-unit on the row's base_power"}, {"table": "synchronous_condensers", "column": "reactive_power", "quantity_kind": "ReactivePower", "unit": "MVAr", "discriminator_column": "power_units", "discriminator_value": "NATURAL_UNITS", "description": "Initial reactive power setpoint in MVAr"}, {"table": "synchronous_condensers", "column": "rating", "quantity_kind": "ApparentPower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "Maximum AC-side output rating, per-unit on the row's base_power"}, @@ -235,6 +246,8 @@ {"table": "synchronous_condensers", "column": "reactive_power_limits", "quantity_kind": "ReactivePower", "unit": "MVAr", "discriminator_column": "power_units", "discriminator_value": "NATURAL_UNITS", "description": "Reactive power limits, JSON {min, max} in MVAr"}, {"table": "synchronous_condensers", "column": "active_power_losses", "quantity_kind": "ActivePower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "Active power loss incurred by being online, per-unit on the row's base_power"}, {"table": "synchronous_condensers", "column": "active_power_losses", "quantity_kind": "ActivePower", "unit": "MW", "discriminator_column": "power_units", "discriminator_value": "NATURAL_UNITS", "description": "Active power loss incurred by being online in MW"}, + {"table": "synchronous_condensers", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "COMPONENT_BASE", "base_voltage_ref": "remote_regulated_bus_id->balancing_topologies.base_voltage", "description": "Voltage held at the regulated bus, per-unit on that bus's base voltage; the regulated bus is remote_regulated_bus_id when set, else bus"}, + {"table": "synchronous_condensers", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "NATURAL_UNITS", "description": "Voltage held at the regulated bus in kV"}, {"table": "sources", "column": "base_voltage", "quantity_kind": "Voltage", "unit": "kV", "description": "Nominal voltage of the source terminal"}, {"table": "sources", "column": "base_power", "quantity_kind": "ApparentPower", "unit": "MVA", "description": "Base power the row's per-unit values resolve against"}, {"table": "sources", "column": "active_power", "quantity_kind": "ActivePower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "Active power injection setpoint, per-unit on the row's base_power"}, @@ -259,6 +272,8 @@ {"table": "sources", "column": "r_th", "quantity_kind": "Resistance", "unit": "ohm", "discriminator_column": "parameter_units", "discriminator_value": "NATURAL_UNITS", "description": "Thevenin resistance in ohm"}, {"table": "sources", "column": "x_th", "quantity_kind": "Reactance", "unit": "pu", "discriminator_column": "parameter_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "base_voltage_ref": "base_voltage", "description": "Thevenin reactance, per-unit on the row's base_power"}, {"table": "sources", "column": "x_th", "quantity_kind": "Reactance", "unit": "ohm", "discriminator_column": "parameter_units", "discriminator_value": "NATURAL_UNITS", "description": "Thevenin reactance in ohm"}, + {"table": "sources", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "COMPONENT_BASE", "base_voltage_ref": "remote_regulated_bus_id->balancing_topologies.base_voltage", "description": "Voltage held at the regulated bus, per-unit on that bus's base voltage; the regulated bus is remote_regulated_bus_id when set, else bus"}, + {"table": "sources", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "voltage_setpoint_units", "discriminator_value": "NATURAL_UNITS", "description": "Voltage held at the regulated bus in kV"}, {"table": "two_terminal_hvdc_lines", "column": "active_power_flow", "quantity_kind": "ActivePower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "Active power flow through the line, per-unit on the row's base_power"}, {"table": "two_terminal_hvdc_lines", "column": "active_power_flow", "quantity_kind": "ActivePower", "unit": "MW", "discriminator_column": "power_units", "discriminator_value": "NATURAL_UNITS", "description": "Active power flow through the line in MW"}, {"table": "two_terminal_hvdc_lines", "column": "active_power_limits_from", "quantity_kind": "ActivePower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "From-terminal active power limits, per-unit on the row's base_power"}, @@ -276,7 +291,6 @@ {"table": "facts_control_devices", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "parameter_units", "discriminator_value": "COMPONENT_BASE", "base_voltage_ref": "bus->balancing_topologies.base_voltage", "description": "Sending-end voltage setpoint, per-unit on the bus's base voltage"}, {"table": "facts_control_devices", "column": "voltage_setpoint", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "parameter_units", "discriminator_value": "NATURAL_UNITS", "description": "Sending-end voltage setpoint in kV"}, {"table": "facts_control_devices", "column": "shunt_control_type", "quantity_kind": "Fraction", "unit": "1", "description": "Device class selecting the reactive-limit law (SVC vs STATCOM)"}, - {"table": "facts_control_devices", "column": "regulated_bus_number", "quantity_kind": "Fraction", "unit": "1", "description": "Bus whose voltage this device regulates; 0 = local bus"}, {"table": "facts_control_devices", "column": "max_reactive_power", "quantity_kind": "ReactivePower", "unit": "pu", "discriminator_column": "power_units", "discriminator_value": "COMPONENT_BASE", "base_power_ref": "base_power", "description": "Independent max reactive power ceiling, per-unit on the row's base_power"}, {"table": "facts_control_devices", "column": "max_reactive_power", "quantity_kind": "ReactivePower", "unit": "MVAr", "discriminator_column": "power_units", "discriminator_value": "NATURAL_UNITS", "description": "Independent max reactive power ceiling in MVAr"}, {"table": "facts_control_devices", "column": "base_power", "quantity_kind": "ApparentPower", "unit": "MVA", "description": "Base power the row's per-unit values resolve against"}, @@ -288,11 +302,16 @@ {"table": "interconnecting_converters", "column": "ac_setpoint", "quantity_kind": "PowerFactor", "unit": "1", "discriminator_column": "ac_control", "discriminator_value": "AC_REACTIVE_POWER", "description": "AC power factor setpoint"}, {"table": "interconnecting_converters", "column": "ac_setpoint", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "ac_control", "discriminator_value": "AC_VOLTAGE", "discriminator_column_2": "parameter_units", "discriminator_value_2": "COMPONENT_BASE", "base_voltage_ref": "bus->balancing_topologies.base_voltage", "description": "AC voltage setpoint, per-unit"}, {"table": "interconnecting_converters", "column": "ac_setpoint", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "ac_control", "discriminator_value": "AC_VOLTAGE", "discriminator_column_2": "parameter_units", "discriminator_value_2": "NATURAL_UNITS", "description": "AC voltage setpoint in kV"}, - {"table": "interconnecting_converters", "column": "rmpct", "quantity_kind": "Fraction", "unit": "1", "description": "Remote-bus voltage-control percentage compensation"}, {"table": "interconnecting_converters", "column": "power_factor_weighting_fraction", "quantity_kind": "Fraction", "unit": "1", "description": "Reactive/active power-factor weighting fraction"}, - {"table": "interconnecting_converters", "column": "remote_bus_control", "quantity_kind": "Fraction", "unit": "1", "description": "Remote bus whose voltage this converter regulates; null = local bus"}, {"table": "interconnecting_converters", "column": "voltage_limits", "quantity_kind": "Voltage", "unit": "pu", "base_voltage_ref": "dc_bus->balancing_topologies.base_voltage", "description": "JSON {min, max} voltage limits"}, {"table": "interconnecting_converters", "column": "base_power", "quantity_kind": "ApparentPower", "unit": "MVA", "description": "Base power the row's per-unit values resolve against"}, + {"table": "voltage_control_groups", "column": "reactive_power_limits", "quantity_kind": "ReactivePower", "unit": "MVAr", "description": "Droop controller reactive power range (JSON min/max, PSS/E QMIN/QMAX)"}, + {"table": "voltage_control_groups", "column": "deadband_reactive_power", "quantity_kind": "ReactivePower", "unit": "MVAr", "description": "Droop controller reactive power held inside the voltage deadband (PSS/E QDB)"}, + {"table": "voltage_control_groups", "column": "deadband_voltage_limits", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "voltage_units", "discriminator_value": "COMPONENT_BASE", "base_voltage_ref": "regulated_bus_id->balancing_topologies.base_voltage", "description": "Droop controller voltage deadband (JSON min/max, PSS/E VDBLOW/VDBHIGH), per-unit on the regulated bus's base voltage"}, + {"table": "voltage_control_groups", "column": "deadband_voltage_limits", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "voltage_units", "discriminator_value": "NATURAL_UNITS", "description": "Droop controller voltage deadband (JSON min/max) in kV"}, + {"table": "voltage_control_groups", "column": "voltage_limits", "quantity_kind": "Voltage", "unit": "pu", "discriminator_column": "voltage_units", "discriminator_value": "COMPONENT_BASE", "base_voltage_ref": "regulated_bus_id->balancing_topologies.base_voltage", "description": "Droop controller voltage range (JSON min/max, PSS/E VLOW/VHIGH), per-unit on the regulated bus's base voltage"}, + {"table": "voltage_control_groups", "column": "voltage_limits", "quantity_kind": "Voltage", "unit": "kV", "discriminator_column": "voltage_units", "discriminator_value": "NATURAL_UNITS", "description": "Droop controller voltage range (JSON min/max) in kV"}, + {"table": "voltage_control_associations", "column": "weight", "quantity_kind": "Dimensionless", "unit": "1", "description": "Relative reactive power share of one voltage control group member (PSS/E RMPCT / 100)"}, {"table": "supply_technologies", "column": "lifetime", "quantity_kind": "CalendarPeriod", "unit": "yr", "description": "Technology lifetime"}, {"table": "supply_technologies", "column": "unit_size", "quantity_kind": "ActivePower", "unit": "MW", "description": "Unit nameplate capacity"}, {"table": "supply_technologies", "column": "capacity_limits", "quantity_kind": "ActivePower", "unit": "MW", "description": "JSON {min, max} capacity limits"}, @@ -364,8 +383,6 @@ {"table": "attributes", "column": "rating_to", "quantity_kind": "ApparentPower", "unit": "MVA", "description": "VSC to-converter rating"}, {"table": "attributes", "column": "reactive_power_from", "quantity_kind": "ReactivePower", "unit": "MVAr", "description": "VSC from-terminal reactive power"}, {"table": "attributes", "column": "reactive_power_to", "quantity_kind": "ReactivePower", "unit": "MVAr", "description": "VSC to-terminal reactive power"}, - {"table": "attributes", "column": "rmpct_from", "quantity_kind": "Fraction", "unit": "1", "description": "VSC from-converter remote-regulation share"}, - {"table": "attributes", "column": "rmpct_to", "quantity_kind": "Fraction", "unit": "1", "description": "VSC to-converter remote-regulation share"}, {"table": "attributes", "column": "power_factor_weighting_fraction_from", "quantity_kind": "Fraction", "unit": "1", "description": "VSC from-converter power-factor weighting"}, {"table": "attributes", "column": "power_factor_weighting_fraction_to", "quantity_kind": "Fraction", "unit": "1", "description": "VSC to-converter power-factor weighting"}, {"table": "attributes", "column": "magnitude", "quantity_kind": "Voltage", "unit": "pu", "description": "Bus voltage magnitude, per-unit on base_voltage"}, diff --git a/schema/coverage_decisions.json b/schema/coverage_decisions.json index 5559f406..0a3f01e4 100644 --- a/schema/coverage_decisions.json +++ b/schema/coverage_decisions.json @@ -278,10 +278,6 @@ "rectifier_tap_step": "attribute", "rectifier_transformer_ratio": "attribute", "rectifier_xc": "attribute", - "remote_bus_control_from": "attribute", - "remote_bus_control_to": "attribute", - "rmpct_from": "attribute", - "rmpct_to": "attribute", "scheduled_dc_voltage": "attribute", "setpoint_voltage_units": "skip", "switch_mode_voltage": "attribute", diff --git a/schema/generated_schema.sql b/schema/generated_schema.sql index c59908d7..577a831f 100644 --- a/schema/generated_schema.sql +++ b/schema/generated_schema.sql @@ -22,6 +22,9 @@ CREATE TABLE thermal_generators ( status TEXT NOT NULL CHECK (status IN ('OFFLINE', 'ONLINE', 'STARTUP', 'SHUTDOWN')), commitment_mode TEXT NULL DEFAULT 'COMMITTED' CHECK (commitment_mode IN ('UNCOMMITTED', 'COMMITTED', 'SELF_SCHEDULED', 'RELIABILITY', 'MUST_RUN')), balancing_topology INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + remote_regulated_bus_id INTEGER NULL, + voltage_setpoint_units TEXT NULL DEFAULT 'COMPONENT_BASE' CHECK (voltage_setpoint_units IN ('NATURAL_UNITS', 'COMPONENT_BASE')), + voltage_setpoint REAL NULL DEFAULT 1.0, -- Units: per voltage_setpoint_units (COMPONENT_BASE: pu, NATURAL_UNITS: kV) active_power REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MW) reactive_power REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) rating REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVA) @@ -44,6 +47,9 @@ CREATE TABLE renewable_generators ( name TEXT NOT NULL UNIQUE, available BOOLEAN NOT NULL, balancing_topology INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + remote_regulated_bus_id INTEGER NULL, + voltage_setpoint_units TEXT NULL DEFAULT 'COMPONENT_BASE' CHECK (voltage_setpoint_units IN ('NATURAL_UNITS', 'COMPONENT_BASE')), + voltage_setpoint REAL NULL DEFAULT 1.0, -- Units: per voltage_setpoint_units (COMPONENT_BASE: pu, NATURAL_UNITS: kV) active_power REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MW) reactive_power REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) rating REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVA) @@ -64,6 +70,9 @@ CREATE TABLE hydro_generators ( name TEXT NOT NULL UNIQUE, available BOOLEAN NOT NULL, balancing_topology INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + remote_regulated_bus_id INTEGER NULL, + voltage_setpoint_units TEXT NULL DEFAULT 'COMPONENT_BASE' CHECK (voltage_setpoint_units IN ('NATURAL_UNITS', 'COMPONENT_BASE')), + voltage_setpoint REAL NULL DEFAULT 1.0, -- Units: per voltage_setpoint_units (COMPONENT_BASE: pu, NATURAL_UNITS: kV) active_power REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MW) reactive_power REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) rating REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVA) @@ -95,6 +104,9 @@ CREATE TABLE storage_units ( name TEXT NOT NULL UNIQUE, available BOOLEAN NOT NULL, balancing_topology INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + remote_regulated_bus_id INTEGER NULL, + voltage_setpoint_units TEXT NULL DEFAULT 'COMPONENT_BASE' CHECK (voltage_setpoint_units IN ('NATURAL_UNITS', 'COMPONENT_BASE')), + voltage_setpoint REAL NULL DEFAULT 1.0, -- Units: per voltage_setpoint_units (COMPONENT_BASE: pu, NATURAL_UNITS: kV) prime_mover_type TEXT NOT NULL CHECK (prime_mover_type IN ('BA', 'BT', 'CA', 'CC', 'CE', 'CP', 'CS', 'CT', 'ES', 'FC', 'FW', 'GT', 'HA', 'HB', 'HK', 'HY', 'IC', 'PS', 'OT', 'ST', 'PVe', 'WT', 'WS')) REFERENCES prime_mover_types (name), storage_technology_type TEXT NOT NULL CHECK (storage_technology_type IN ('PTES', 'LIB', 'LAB', 'FLWB', 'SIB', 'ZIB', 'HGS', 'LAES', 'OTHER_CHEM', 'OTHER_MECH', 'OTHER_THERM')) REFERENCES storage_technology_types (name), storage_capacity REAL NOT NULL, -- Units: per energy_units (MWH: MWh, MWMIN: MWmin) @@ -238,7 +250,10 @@ CREATE TABLE transformer_circuits ( r REAL NULL DEFAULT 0.0, -- Units: per parameter_units (COMPONENT_BASE: pu, NATURAL_UNITS: ohm) x REAL NULL DEFAULT 0.0, -- Units: per parameter_units (COMPONENT_BASE: pu, NATURAL_UNITS: ohm) control_objective TEXT NULL DEFAULT 'UNDEFINED' CHECK (control_objective IN ('UNDEFINED', 'VOLTAGE_DISABLED', 'REACTIVE_POWER_FLOW_DISABLED', 'ACTIVE_POWER_FLOW_DISABLED', 'CONTROL_OF_DC_LINE_DISABLED', 'ASYMMETRIC_ACTIVE_POWER_FLOW_DISABLED', 'FIXED', 'VOLTAGE', 'REACTIVE_POWER_FLOW', 'ACTIVE_POWER_FLOW', 'CONTROL_OF_DC_LINE', 'ASYMMETRIC_ACTIVE_POWER_FLOW')), - regulated_bus_number INTEGER NULL DEFAULT 0, + regulated_bus_id INTEGER NULL, + regulated_bus_side TEXT NULL DEFAULT 'UNDEFINED' CHECK (regulated_bus_side IN ('UNDEFINED', 'CONTROLLING_WINDING', 'OPPOSITE_WINDING')), + load_drop_compensation_r REAL NULL DEFAULT 0.0, -- Units: per parameter_units (COMPONENT_BASE: pu, NATURAL_UNITS: ohm) + load_drop_compensation_x REAL NULL DEFAULT 0.0, -- Units: per parameter_units (COMPONENT_BASE: pu, NATURAL_UNITS: ohm) control_limits JSON NULL DEFAULT '{"max":1.1,"min":0.9}', -- Units: per control_objective (ACTIVE_POWER_FLOW: rad, ACTIVE_POWER_FLOW_DISABLED: rad, ASYMMETRIC_ACTIVE_POWER_FLOW: rad, ASYMMETRIC_ACTIVE_POWER_FLOW_DISABLED: rad, CONTROL_OF_DC_LINE: 1, CONTROL_OF_DC_LINE_DISABLED: 1, FIXED: 1, REACTIVE_POWER_FLOW: 1, REACTIVE_POWER_FLOW_DISABLED: 1, UNDEFINED: 1, VOLTAGE: 1, VOLTAGE_DISABLED: 1) controlled_quantity_limits JSON NULL DEFAULT '{"max":1.1,"min":0.9}', -- Units: per control_objective (ACTIVE_POWER_FLOW: MW, ACTIVE_POWER_FLOW_DISABLED: MW, ASYMMETRIC_ACTIVE_POWER_FLOW: MW, ASYMMETRIC_ACTIVE_POWER_FLOW_DISABLED: MW, CONTROL_OF_DC_LINE: MW, CONTROL_OF_DC_LINE_DISABLED: MW, FIXED: pu, REACTIVE_POWER_FLOW: MVAr, REACTIVE_POWER_FLOW_DISABLED: MVAr, UNDEFINED: pu, VOLTAGE: pu, VOLTAGE_DISABLED: pu) number_of_tap_positions INTEGER NULL DEFAULT 33, @@ -286,7 +301,7 @@ CREATE TABLE three_winding_transformers ( -- two_terminal_hvdc_lines: generated from TwoTerminalGenericHVDCLine, TwoTerminalLCCLine, TwoTerminalVSCLine -- Stored via the generic `attributes` table (registered attribute-name --- conventions), not as columns: loss, r, transfer_setpoint, scheduled_dc_voltage, rectifier_bridges, rectifier_delay_angle, rectifier_delay_angle_limits, rectifier_rc, rectifier_xc, rectifier_base_voltage, rectifier_transformer_ratio, rectifier_tap_setting, rectifier_tap_limits, rectifier_tap_step, rectifier_capacitor_reactance, inverter_bridges, inverter_extinction_angle, inverter_extinction_angle_limits, inverter_rc, inverter_xc, inverter_base_voltage, inverter_transformer_ratio, inverter_tap_setting, inverter_tap_limits, inverter_tap_step, inverter_capacitor_reactance, power_mode, switch_mode_voltage, compounding_resistance, min_compounding_voltage, rating, rating_from, rating_to, g, dc_current, rated_dc_voltage, reactive_power_from, reactive_power_to, dc_control_from, dc_control_to, ac_control_from, ac_control_to, dc_setpoint_from, dc_setpoint_to, ac_setpoint_from, ac_setpoint_to, converter_loss_from, converter_loss_to, max_dc_current_from, max_dc_current_to, power_factor_weighting_fraction_from, power_factor_weighting_fraction_to, voltage_limits_from, voltage_limits_to, dc_voltage_droop_from, dc_voltage_droop_to, remote_bus_control_from, remote_bus_control_to, rated_ac_voltage_from, rated_ac_voltage_to, rmpct_from, rmpct_to +-- conventions), not as columns: loss, r, transfer_setpoint, scheduled_dc_voltage, rectifier_bridges, rectifier_delay_angle, rectifier_delay_angle_limits, rectifier_rc, rectifier_xc, rectifier_base_voltage, rectifier_transformer_ratio, rectifier_tap_setting, rectifier_tap_limits, rectifier_tap_step, rectifier_capacitor_reactance, inverter_bridges, inverter_extinction_angle, inverter_extinction_angle_limits, inverter_rc, inverter_xc, inverter_base_voltage, inverter_transformer_ratio, inverter_tap_setting, inverter_tap_limits, inverter_tap_step, inverter_capacitor_reactance, power_mode, switch_mode_voltage, compounding_resistance, min_compounding_voltage, rating, rating_from, rating_to, g, dc_current, rated_dc_voltage, reactive_power_from, reactive_power_to, dc_control_from, dc_control_to, ac_control_from, ac_control_to, dc_setpoint_from, dc_setpoint_to, ac_setpoint_from, ac_setpoint_to, converter_loss_from, converter_loss_to, max_dc_current_from, max_dc_current_to, power_factor_weighting_fraction_from, power_factor_weighting_fraction_to, voltage_limits_from, voltage_limits_to, dc_voltage_droop_from, dc_voltage_droop_to, rated_ac_voltage_from, rated_ac_voltage_to CREATE TABLE two_terminal_hvdc_lines ( id INTEGER PRIMARY KEY REFERENCES entities (id) ON DELETE CASCADE, name TEXT NOT NULL UNIQUE, @@ -298,7 +313,13 @@ CREATE TABLE two_terminal_hvdc_lines ( reactive_power_limits_from JSON NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) reactive_power_limits_to JSON NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) base_power REAL NOT NULL, -- Units: MVA - power_units TEXT NOT NULL CHECK (power_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')) + power_units TEXT NOT NULL CHECK (power_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), + rectifier_commutating_bus_id INTEGER NULL, + inverter_commutating_bus_id INTEGER NULL, + rectifier_tap_transformer_id INTEGER NULL, + inverter_tap_transformer_id INTEGER NULL, + remote_regulated_bus_id_from INTEGER NULL, + remote_regulated_bus_id_to INTEGER NULL ); -- tmodel_hvdc_lines: generated from TModelHVDCLine @@ -323,6 +344,9 @@ CREATE TABLE synchronous_condensers ( name TEXT NOT NULL UNIQUE, available BOOLEAN NOT NULL, bus INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + remote_regulated_bus_id INTEGER NULL, + voltage_setpoint_units TEXT NULL DEFAULT 'COMPONENT_BASE' CHECK (voltage_setpoint_units IN ('NATURAL_UNITS', 'COMPONENT_BASE')), + voltage_setpoint REAL NULL DEFAULT 1.0, -- Units: per voltage_setpoint_units (COMPONENT_BASE: pu, NATURAL_UNITS: kV) reactive_power REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) rating REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVA) reactive_power_limits JSON NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) @@ -355,7 +379,7 @@ CREATE TABLE switched_admittance ( solved_admittance REAL NULL, -- Units: per admittance_units (COMPONENT_MVAR: MVAr, NATURAL_UNITS: S) admittance_limits JSON NULL DEFAULT '{"max":1.0,"min":1.0}', -- Units: per admittance_units (COMPONENT_MVAR: MVAr, NATURAL_UNITS: S) control_mode TEXT NULL DEFAULT 'FIXED' CHECK (control_mode IN ('UNDEFINED', 'FIXED', 'DISCRETE_VOLTAGE', 'CONTINUOUS_VOLTAGE', 'DISCRETE_REACTIVE_PLANT', 'DISCRETE_REACTIVE_VSC', 'DISCRETE_ADMITTANCE_REMOTE')), - regulated_bus_number INTEGER NULL DEFAULT 0 -- Units: 1 + remote_regulated_bus_id INTEGER NULL ); -- sources: generated from Source @@ -364,6 +388,9 @@ CREATE TABLE sources ( name TEXT NOT NULL UNIQUE, available BOOLEAN NOT NULL, bus INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + remote_regulated_bus_id INTEGER NULL, + voltage_setpoint_units TEXT NULL DEFAULT 'COMPONENT_BASE' CHECK (voltage_setpoint_units IN ('NATURAL_UNITS', 'COMPONENT_BASE')), + voltage_setpoint REAL NULL DEFAULT 1.0, -- Units: per voltage_setpoint_units (COMPONENT_BASE: pu, NATURAL_UNITS: kV) active_power REAL NULL DEFAULT 0.0, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MW) reactive_power REAL NULL DEFAULT 0.0, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) active_power_limits JSON NULL DEFAULT '{"max":0.0,"min":0.0}', -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MW) @@ -401,8 +428,7 @@ CREATE TABLE interconnecting_converters ( dc_setpoint REAL NULL DEFAULT 0.0, -- Units: per dc_control (DC_POWER: MW; DC_VOLTAGE: per parameter_units [COMPONENT_BASE: pu, NATURAL_UNITS: kV]; DC_VOLTAGE_DROOP: per parameter_units [COMPONENT_BASE: pu, NATURAL_UNITS: kV]) ac_setpoint REAL NULL DEFAULT 1.0, -- Units: per ac_control (AC_REACTIVE_POWER: 1; AC_VOLTAGE: per parameter_units [COMPONENT_BASE: pu, NATURAL_UNITS: kV]) dc_voltage_droop REAL NULL DEFAULT 0.0, -- Units: pu - remote_bus_control INTEGER NULL, - rmpct REAL NULL DEFAULT 100.0, -- Units: 1 + remote_regulated_bus_id INTEGER NULL, power_factor_weighting_fraction REAL NULL DEFAULT 1.0, -- Units: 1 voltage_limits JSON NULL DEFAULT '{"max":999.9,"min":0.0}', -- Units: pu dynamic_injector INTEGER NULL @@ -418,10 +444,10 @@ CREATE TABLE facts_control_devices ( parameter_units TEXT NULL DEFAULT 'COMPONENT_BASE' CHECK (parameter_units IN ('NATURAL_UNITS', 'COMPONENT_BASE')), voltage_setpoint REAL NOT NULL, -- Units: per parameter_units (COMPONENT_BASE: pu, NATURAL_UNITS: kV) max_shunt_current REAL NOT NULL, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVA) - reactive_power_required REAL NOT NULL, -- Units: 1 + reactive_power_required REAL NULL DEFAULT 0.0, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) max_reactive_power REAL NULL DEFAULT 9999.0, -- Units: per power_units (COMPONENT_BASE: pu, NATURAL_UNITS: MVAr) shunt_control_type TEXT NULL DEFAULT 'STATCOM' CHECK (shunt_control_type IN ('SVC', 'STATCOM')), - regulated_bus_number INTEGER NULL DEFAULT 0, -- Units: 1 + remote_regulated_bus_id INTEGER NULL, base_power REAL NOT NULL, -- Units: MVA power_units TEXT NOT NULL CHECK (power_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), dynamic_injector INTEGER NULL diff --git a/schema/schema.sql b/schema/schema.sql index 20c7bdea..11ae4f8f 100644 --- a/schema/schema.sql +++ b/schema/schema.sql @@ -1,6 +1,6 @@ -- Requires SQLite >= 3.45. Test-only: drops every table below, so never run -- against a live dataset. -PRAGMA user_version = 1; -- first released schema version; bump on every schema or registry change +PRAGMA user_version = 2; -- bump on every schema or registry change DROP TABLE IF EXISTS thermal_generators; @@ -84,6 +84,10 @@ DROP TABLE IF EXISTS supplemental_attribute_associations; DROP TABLE IF EXISTS transport_technologies; +DROP TABLE IF EXISTS voltage_control_associations; + +DROP TABLE IF EXISTS voltage_control_groups; + DROP TABLE IF EXISTS combined_cycle_associations; DROP TABLE IF EXISTS plant_associations; @@ -251,8 +255,17 @@ CREATE TABLE transformer_circuits ( 'ASYMMETRIC_ACTIVE_POWER_FLOW_DISABLED', 'FIXED', 'VOLTAGE', 'REACTIVE_POWER_FLOW', 'ACTIVE_POWER_FLOW', 'CONTROL_OF_DC_LINE', 'ASYMMETRIC_ACTIVE_POWER_FLOW')), - -- Controlled bus number (sign = regulation side): - regulated_bus_number INTEGER NOT NULL DEFAULT 0, + -- Bus whose voltage the tap holds, named exactly when control_objective regulates + -- voltage. regulated_bus_side says which winding the bus lies beyond when it is not an + -- end of the circuit's arc; for an arc end the side follows from the arc and must be + -- UNDEFINED (enforce_transformer_circuits_regulated_bus_side_*). + regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL, + regulated_bus_side TEXT NOT NULL DEFAULT 'UNDEFINED' + CHECK (regulated_bus_side IN ('UNDEFINED', 'CONTROLLING_WINDING', 'OPPOSITE_WINDING')), + -- Load drop compensation CR + jCX: the impedance times the circuit current that + -- corrects the regulated voltage. + load_drop_compensation_r REAL NOT NULL DEFAULT 0.0, -- Units: per parameter_units + load_drop_compensation_x REAL NOT NULL DEFAULT 0.0, -- Units: per parameter_units control_limits TEXT NULL DEFAULT '{"min": 0.9, "max": 1.1}' CHECK (control_limits IS NULL OR json_valid(control_limits)), -- Units: per control_objective (tap ratio 1 / angle rad) controlled_quantity_limits TEXT NULL DEFAULT '{"min": 0.9, "max": 1.1}' @@ -266,7 +279,9 @@ CREATE TABLE transformer_circuits ( base_power REAL NOT NULL CHECK (base_power > 0), -- Units: MVA power_units TEXT NOT NULL CHECK (power_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), base_voltage_primary REAL NULL CHECK (base_voltage_primary > 0), -- Units: kV - base_voltage_secondary REAL NULL CHECK (base_voltage_secondary > 0) -- Units: kV + base_voltage_secondary REAL NULL CHECK (base_voltage_secondary > 0), -- Units: kV + CHECK ((regulated_bus_id IS NOT NULL) = (control_objective IN ('VOLTAGE', 'VOLTAGE_DISABLED'))), + CHECK (regulated_bus_side = 'UNDEFINED' OR regulated_bus_id IS NOT NULL) ) strict; -- Two-winding transformer (PSY TwoWindingTransformer); series data lives on @@ -341,6 +356,13 @@ CREATE TABLE thermal_generators ( prime_mover_type TEXT NOT NULL REFERENCES prime_mover_types(name), fuel TEXT NOT NULL DEFAULT 'OTHER' REFERENCES fuels(name), balancing_topology INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + -- Bus whose voltage the unit holds while it regulates voltage. NULL means its own + -- bus, so naming the own bus is rejected; the reference blocks deleting that bus. + remote_regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL + CHECK (remote_regulated_bus_id IS NULL OR remote_regulated_bus_id <> balancing_topology), + voltage_setpoint REAL NOT NULL DEFAULT 1.0 CHECK (voltage_setpoint > 0), -- Units: per voltage_setpoint_units + voltage_setpoint_units TEXT NOT NULL DEFAULT 'COMPONENT_BASE' + CHECK (voltage_setpoint_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), rating REAL NOT NULL CHECK (rating >= 0), -- Units: per power_units base_power REAL NOT NULL CHECK (base_power > 0), power_units TEXT NOT NULL CHECK (power_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), @@ -389,6 +411,13 @@ CREATE TABLE renewable_generators ( name TEXT NOT NULL UNIQUE, prime_mover_type TEXT NOT NULL REFERENCES prime_mover_types(name), balancing_topology INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + -- Bus whose voltage the unit holds while it regulates voltage. NULL means its own + -- bus, so naming the own bus is rejected; the reference blocks deleting that bus. + remote_regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL + CHECK (remote_regulated_bus_id IS NULL OR remote_regulated_bus_id <> balancing_topology), + voltage_setpoint REAL NOT NULL DEFAULT 1.0 CHECK (voltage_setpoint > 0), -- Units: per voltage_setpoint_units + voltage_setpoint_units TEXT NOT NULL DEFAULT 'COMPONENT_BASE' + CHECK (voltage_setpoint_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), rating REAL NOT NULL CHECK (rating >= 0), -- Units: per power_units base_power REAL NOT NULL CHECK (base_power > 0), power_units TEXT NOT NULL CHECK (power_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), @@ -427,6 +456,13 @@ CREATE TABLE hydro_generators ( name TEXT NOT NULL UNIQUE, prime_mover_type TEXT NOT NULL DEFAULT 'HY' REFERENCES prime_mover_types(name), balancing_topology INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + -- Bus whose voltage the unit holds while it regulates voltage. NULL means its own + -- bus, so naming the own bus is rejected; the reference blocks deleting that bus. + remote_regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL + CHECK (remote_regulated_bus_id IS NULL OR remote_regulated_bus_id <> balancing_topology), + voltage_setpoint REAL NOT NULL DEFAULT 1.0 CHECK (voltage_setpoint > 0), -- Units: per voltage_setpoint_units + voltage_setpoint_units TEXT NOT NULL DEFAULT 'COMPONENT_BASE' + CHECK (voltage_setpoint_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), rating REAL NOT NULL CHECK (rating >= 0), -- Units: per power_units base_power REAL NOT NULL CHECK (base_power > 0), power_units TEXT NOT NULL CHECK (power_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), @@ -475,6 +511,13 @@ CREATE TABLE storage_units ( prime_mover_type TEXT NOT NULL REFERENCES prime_mover_types(name), storage_technology_type TEXT NOT NULL REFERENCES storage_technology_types(name), balancing_topology INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + -- Bus whose voltage the unit holds while it regulates voltage. NULL means its own + -- bus, so naming the own bus is rejected; the reference blocks deleting that bus. + remote_regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL + CHECK (remote_regulated_bus_id IS NULL OR remote_regulated_bus_id <> balancing_topology), + voltage_setpoint REAL NOT NULL DEFAULT 1.0 CHECK (voltage_setpoint > 0), -- Units: per voltage_setpoint_units + voltage_setpoint_units TEXT NOT NULL DEFAULT 'COMPONENT_BASE' + CHECK (voltage_setpoint_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), rating REAL NOT NULL CHECK (rating >= 0), -- Units: per power_units base_power REAL NOT NULL CHECK (base_power > 0), power_units TEXT NOT NULL CHECK (power_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), @@ -716,6 +759,58 @@ CREATE TABLE combined_cycle_associations ( UNIQUE (plant_id, entity_id, hrsg_index) ) strict; +-- Voltage droop controllers and reactive power sharing groups (PSY VoltageDroopControl +-- and ReactivePowerSharing): supplemental attributes whose members jointly regulate one +-- bus. Modeled on plants: TYPE discriminates the two kinds. The columns after available +-- describe the droop controller's Q-V characteristic, which a sharing group does not +-- have, so they are set exactly on a droop row. Reactive powers are natural MVAr: the +-- attribute has no base_power to per-unitize against. Voltages follow voltage_units +-- (COMPONENT_BASE: pu on the regulated bus's base_voltage; NATURAL_UNITS: kV). +CREATE TABLE voltage_control_groups ( + id INTEGER PRIMARY KEY REFERENCES entities (id) ON DELETE CASCADE, + name TEXT NOT NULL UNIQUE, + TYPE TEXT NOT NULL CHECK (TYPE IN ('VoltageDroopControl', 'ReactivePowerSharing')), + available INTEGER NOT NULL DEFAULT 1 CHECK (available IN (0, 1)), + regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + reactive_power_limits TEXT NULL -- {"min": ..., "max": ...} + CHECK (reactive_power_limits IS NULL OR json_valid(reactive_power_limits)), -- Units: MVAr + deadband_reactive_power REAL NULL, -- Units: MVAr + voltage_units TEXT NOT NULL DEFAULT 'COMPONENT_BASE' + CHECK (voltage_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), + deadband_voltage_limits TEXT NULL -- {"min": ..., "max": ...} + CHECK (deadband_voltage_limits IS NULL OR json_valid(deadband_voltage_limits)), -- Units: per voltage_units + voltage_limits TEXT NULL -- {"min": ..., "max": ...} + CHECK (voltage_limits IS NULL OR json_valid(voltage_limits)), -- Units: per voltage_units + CHECK ((TYPE = 'VoltageDroopControl') = (regulated_bus_id IS NOT NULL + AND reactive_power_limits IS NOT NULL AND deadband_reactive_power IS NOT NULL + AND deadband_voltage_limits IS NOT NULL AND voltage_limits IS NOT NULL)), + CHECK (TYPE = 'VoltageDroopControl' OR (regulated_bus_id IS NULL + AND reactive_power_limits IS NULL AND deadband_reactive_power IS NULL + AND deadband_voltage_limits IS NULL AND voltage_limits IS NULL)) +) strict; + +-- One row per member of a voltage control group: entity_id is the regulating device, or +-- a two-terminal VSC line with terminal naming the converter (UNDEFINED for any other +-- member). weight is the member's +-- relative reactive power share (PSS/E RMPCT / 100); shares are relative, so a group's +-- rows need not sum to one. Which entities may join, and when terminal is required, is +-- enforced by enforce_voltage_control_member_types_*. Like plant_associations, the +-- relationship is GridDB's own, so it gets a local AUTOINCREMENT surrogate. +CREATE TABLE voltage_control_associations ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + control_id INTEGER NOT NULL REFERENCES voltage_control_groups (id) ON DELETE CASCADE, + entity_id INTEGER NOT NULL REFERENCES entities (id) ON DELETE CASCADE, + weight REAL NOT NULL DEFAULT 1.0 CHECK (weight > 0), -- Units: 1 + terminal TEXT NOT NULL DEFAULT 'UNDEFINED' CHECK (terminal IN ('UNDEFINED', 'FROM', 'TO')) +) strict; + +-- A device, or one converter of a VSC line, belongs to at most one group of either kind. +CREATE UNIQUE INDEX uq_voltage_control_member + ON voltage_control_associations (entity_id, terminal); + +CREATE INDEX idx_voltage_control_assoc_control + ON voltage_control_associations (control_id, entity_id); + -- Mirrors infrastore's catalog table column-for-column so a row deserializes -- straight into a store, and projects onto the SiennaSchemas wire form. -- owner_category / time_series_type hold the wire string spelling directly @@ -912,8 +1007,10 @@ CREATE TABLE switched_admittance ( CHECK (control_mode IN ('UNDEFINED', 'FIXED', 'DISCRETE_VOLTAGE', 'CONTINUOUS_VOLTAGE', 'DISCRETE_REACTIVE_PLANT', 'DISCRETE_REACTIVE_VSC', 'DISCRETE_ADMITTANCE_REMOTE')), - -- 0 = local bus: - regulated_bus_number INTEGER NOT NULL DEFAULT 0 + -- Bus whose voltage the device regulates; NULL means its own bus, so naming the own + -- bus is rejected. A shared regulation weight lives on voltage_control_associations: + remote_regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL + CHECK (remote_regulated_bus_id IS NULL OR remote_regulated_bus_id <> bus) ) strict; -- Synchronous machine for inertia or reactive support (PSY SynchronousCondenser). @@ -924,6 +1021,13 @@ CREATE TABLE synchronous_condensers ( id INTEGER PRIMARY KEY REFERENCES entities (id) ON DELETE CASCADE, name TEXT NOT NULL UNIQUE, bus INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + -- Bus whose voltage the unit holds while it regulates voltage. NULL means its own + -- bus, so naming the own bus is rejected; the reference blocks deleting that bus. + remote_regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL + CHECK (remote_regulated_bus_id IS NULL OR remote_regulated_bus_id <> bus), + voltage_setpoint REAL NOT NULL DEFAULT 1.0 CHECK (voltage_setpoint > 0), -- Units: per voltage_setpoint_units + voltage_setpoint_units TEXT NOT NULL DEFAULT 'COMPONENT_BASE' + CHECK (voltage_setpoint_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), available INTEGER NOT NULL DEFAULT 1 CHECK (available IN (0, 1)), reactive_power REAL NOT NULL DEFAULT 0.0, -- Units: per power_units rating REAL NOT NULL CHECK (rating > 0), -- Units: per power_units @@ -944,6 +1048,13 @@ CREATE TABLE sources ( id INTEGER PRIMARY KEY REFERENCES entities (id) ON DELETE CASCADE, name TEXT NOT NULL UNIQUE, bus INTEGER NOT NULL REFERENCES balancing_topologies (id) ON DELETE CASCADE, + -- Bus whose voltage the unit holds while it regulates voltage. NULL means its own + -- bus, so naming the own bus is rejected; the reference blocks deleting that bus. + remote_regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL + CHECK (remote_regulated_bus_id IS NULL OR remote_regulated_bus_id <> bus), + voltage_setpoint REAL NOT NULL DEFAULT 1.0 CHECK (voltage_setpoint > 0), -- Units: per voltage_setpoint_units + voltage_setpoint_units TEXT NOT NULL DEFAULT 'COMPONENT_BASE' + CHECK (voltage_setpoint_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), available INTEGER NOT NULL DEFAULT 1 CHECK (available IN (0, 1)), -- Nullable: a source may take the voltage of the bus it connects to instead: base_voltage REAL NULL CHECK (base_voltage IS NULL OR base_voltage > 0), -- Units: kV @@ -1056,7 +1167,22 @@ CREATE TABLE two_terminal_hvdc_lines ( reactive_power_limits_from TEXT NULL CHECK (reactive_power_limits_from IS NULL OR json_valid(reactive_power_limits_from)), -- Units: per power_units reactive_power_limits_to TEXT NULL - CHECK (reactive_power_limits_to IS NULL OR json_valid(reactive_power_limits_to)) -- Units: per power_units + CHECK (reactive_power_limits_to IS NULL OR json_valid(reactive_power_limits_to)), -- Units: per power_units + -- VSC only: the bus each converter's AC voltage control holds. NULL means its own + -- terminal bus, which enforce_two_terminal_hvdc_lines_remote_bus_* forbids naming. + remote_regulated_bus_id_from INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL, + remote_regulated_bus_id_to INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL, + -- LCC only: each converter's commutating bus and tap transformer, when they are not + -- its terminal bus and an ideal transformer. + rectifier_commutating_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL, + inverter_commutating_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL, + rectifier_tap_transformer_id INTEGER NULL REFERENCES two_winding_transformers (id) ON DELETE SET NULL, + inverter_tap_transformer_id INTEGER NULL REFERENCES two_winding_transformers (id) ON DELETE SET NULL, + CHECK (converter_type = 'VSC' + OR (remote_regulated_bus_id_from IS NULL AND remote_regulated_bus_id_to IS NULL)), + CHECK (converter_type = 'LCC' + OR (rectifier_commutating_bus_id IS NULL AND inverter_commutating_bus_id IS NULL + AND rectifier_tap_transformer_id IS NULL AND inverter_tap_transformer_id IS NULL)) ) strict; -- T-model HVDC line (PSY TModelHVDCLine): a DC-network element whose arc @@ -1096,8 +1222,10 @@ CREATE TABLE facts_control_devices ( max_reactive_power REAL NOT NULL DEFAULT 9999.0 CHECK (max_reactive_power >= 0), -- Units: per power_units shunt_control_type TEXT NOT NULL DEFAULT 'STATCOM' CHECK (shunt_control_type IN ('SVC', 'STATCOM')), - -- 0 = local (sending) bus: - regulated_bus_number INTEGER NOT NULL DEFAULT 0 + -- Bus whose voltage the device regulates; NULL means its own bus, so naming the own + -- bus is rejected. A shared regulation weight lives on voltage_control_associations: + remote_regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL + CHECK (remote_regulated_bus_id IS NULL OR remote_regulated_bus_id <> bus) ) strict; -- Interconnecting power converter (PSY InterconnectingConverter), an AC<->DC @@ -1120,9 +1248,10 @@ CREATE TABLE interconnecting_converters ( parameter_units TEXT NOT NULL DEFAULT 'COMPONENT_BASE' CHECK (parameter_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), base_power REAL NOT NULL CHECK (base_power > 0), -- Units: MVA power_units TEXT NOT NULL CHECK (power_units IN ('COMPONENT_BASE', 'NATURAL_UNITS')), - -- Remote-bus voltage control, droop, and power-factor weighting: - remote_bus_control INTEGER NULL CHECK (remote_bus_control IS NULL OR remote_bus_control >= 1), - rmpct REAL NOT NULL DEFAULT 100.0 CHECK (rmpct >= 0), + -- Bus whose voltage the device regulates; NULL means its own bus, so naming the own + -- bus is rejected. A shared regulation weight lives on voltage_control_associations: + remote_regulated_bus_id INTEGER NULL REFERENCES balancing_topologies (id) ON DELETE SET NULL + CHECK (remote_regulated_bus_id IS NULL OR remote_regulated_bus_id <> bus), power_factor_weighting_fraction REAL NOT NULL DEFAULT 1.0 CHECK (power_factor_weighting_fraction >= 0), voltage_limits TEXT NULL DEFAULT '{"min": 0.0, "max": 999.9}' CHECK (voltage_limits IS NULL OR json_valid(voltage_limits)), diff --git a/schema/sql_codegen_map.json b/schema/sql_codegen_map.json index 06b3fea3..dff35ac5 100644 --- a/schema/sql_codegen_map.json +++ b/schema/sql_codegen_map.json @@ -179,12 +179,8 @@ "voltage_limits_to", "dc_voltage_droop_from", "dc_voltage_droop_to", - "remote_bus_control_from", - "remote_bus_control_to", "rated_ac_voltage_from", - "rated_ac_voltage_to", - "rmpct_from", - "rmpct_to" + "rated_ac_voltage_to" ], "skip": [ "parameter_units", diff --git a/schema/triggers.sql b/schema/triggers.sql index c1724fef..2c6f8ca0 100644 --- a/schema/triggers.sql +++ b/schema/triggers.sql @@ -2554,3 +2554,150 @@ SELECT ); END; + +-- Voltage control groups: entity supertype boilerplate. +CREATE TRIGGER IF NOT EXISTS check_voltage_control_groups_entity_exists BEFORE +INSERT ON voltage_control_groups + WHEN NOT EXISTS ( + SELECT 1 + FROM entities + WHERE id = NEW.id + AND entity_table = 'voltage_control_groups' + ) BEGIN +SELECT RAISE( + ABORT, + 'voltage_control_groups.id must exist in entities with entity_table voltage_control_groups before insert' + ); +END; + +CREATE TRIGGER IF NOT EXISTS delete_voltage_control_groups_entity +AFTER DELETE ON voltage_control_groups +FOR EACH ROW +BEGIN + DELETE FROM entities WHERE id = OLD.id; +END; + +-- A transformer circuit regulating an end of its own arc derives the side from the arc +-- and stores none; regulating any other bus needs the side to say which winding the bus +-- lies beyond. +CREATE TRIGGER IF NOT EXISTS enforce_transformer_circuits_regulated_bus_side_insert BEFORE +INSERT ON transformer_circuits + WHEN NEW.regulated_bus_id IS NOT NULL + AND (NEW.regulated_bus_side = 'UNDEFINED') <> EXISTS ( + SELECT 1 + FROM arcs + WHERE id = NEW.arc_id + AND NEW.regulated_bus_id IN (from_id, to_id) + ) +BEGIN +SELECT RAISE( + ABORT, + 'transformer_circuits.regulated_bus_side must be UNDEFINED when regulated_bus_id is an end of the circuit''s arc and a winding side when it is not' + ); +END; + +CREATE TRIGGER IF NOT EXISTS enforce_transformer_circuits_regulated_bus_side_update BEFORE +UPDATE OF regulated_bus_id, regulated_bus_side, arc_id ON transformer_circuits + WHEN NEW.regulated_bus_id IS NOT NULL + AND (NEW.regulated_bus_side = 'UNDEFINED') <> EXISTS ( + SELECT 1 + FROM arcs + WHERE id = NEW.arc_id + AND NEW.regulated_bus_id IN (from_id, to_id) + ) +BEGIN +SELECT RAISE( + ABORT, + 'transformer_circuits.regulated_bus_side must be UNDEFINED when regulated_bus_id is an end of the circuit''s arc and a winding side when it is not' + ); +END; + +-- A VSC converter's remote regulated bus is never its own terminal bus: NULL already means +-- own-bus control. +CREATE TRIGGER IF NOT EXISTS enforce_two_terminal_hvdc_lines_remote_bus_insert +AFTER INSERT ON two_terminal_hvdc_lines +BEGIN +SELECT + CASE + WHEN NEW.remote_regulated_bus_id_from = (SELECT from_id FROM arcs WHERE id = NEW.arc_id) + THEN RAISE(ABORT, 'two_terminal_hvdc_lines.remote_regulated_bus_id_from must not be the from converter''s own terminal bus; NULL means own-bus control') + WHEN NEW.remote_regulated_bus_id_to = (SELECT to_id FROM arcs WHERE id = NEW.arc_id) + THEN RAISE(ABORT, 'two_terminal_hvdc_lines.remote_regulated_bus_id_to must not be the to converter''s own terminal bus; NULL means own-bus control') + END; +END; + +CREATE TRIGGER IF NOT EXISTS enforce_two_terminal_hvdc_lines_remote_bus_update +AFTER UPDATE OF remote_regulated_bus_id_from, remote_regulated_bus_id_to, arc_id ON two_terminal_hvdc_lines +BEGIN +SELECT + CASE + WHEN NEW.remote_regulated_bus_id_from = (SELECT from_id FROM arcs WHERE id = NEW.arc_id) + THEN RAISE(ABORT, 'two_terminal_hvdc_lines.remote_regulated_bus_id_from must not be the from converter''s own terminal bus; NULL means own-bus control') + WHEN NEW.remote_regulated_bus_id_to = (SELECT to_id FROM arcs WHERE id = NEW.arc_id) + THEN RAISE(ABORT, 'two_terminal_hvdc_lines.remote_regulated_bus_id_to must not be the to converter''s own terminal bus; NULL means own-bus control') + END; +END; + +-- Voltage control membership. Droop members are generators; sharing members are the +-- devices that regulate voltage (RenewableNonDispatch shares the renewable table but is a +-- fixed injection); terminal names a converter of a two-terminal VSC line and is +-- UNDEFINED for anything else. The member's table comes from entities, the same source the arc trigger reads. +CREATE TRIGGER IF NOT EXISTS enforce_voltage_control_member_types_insert +AFTER INSERT ON voltage_control_associations +BEGIN +SELECT + CASE + WHEN (SELECT TYPE FROM voltage_control_groups WHERE id = NEW.control_id) = 'VoltageDroopControl' + AND NOT EXISTS ( + SELECT 1 FROM entities + WHERE id = NEW.entity_id + AND entity_table IN ('thermal_generators', 'hydro_generators', 'renewable_generators') + AND entity_type <> 'RenewableNonDispatch' + ) + THEN RAISE(ABORT, 'voltage_control_associations: only generators can be members of a VoltageDroopControl') + WHEN NOT EXISTS ( + SELECT 1 FROM entities + WHERE id = NEW.entity_id + AND entity_table IN ('thermal_generators', 'hydro_generators', 'renewable_generators', + 'storage_units', 'synchronous_condensers', 'sources', 'switched_admittance', + 'facts_control_devices', 'interconnecting_converters', 'two_terminal_hvdc_lines') + AND entity_type <> 'RenewableNonDispatch' + ) + THEN RAISE(ABORT, 'voltage_control_associations: the member must be a voltage regulating device (generator, storage unit, synchronous condenser, source, switched shunt, FACTS device, converter or two-terminal VSC line)') + WHEN (NEW.terminal = 'UNDEFINED') = COALESCE( + (SELECT converter_type FROM two_terminal_hvdc_lines WHERE id = NEW.entity_id) = 'VSC', + 0 + ) + THEN RAISE(ABORT, 'voltage_control_associations.terminal names the converter of a two-terminal VSC line member and must be UNDEFINED for any other member') + END; +END; + +CREATE TRIGGER IF NOT EXISTS enforce_voltage_control_member_types_update +AFTER UPDATE OF control_id, entity_id, terminal ON voltage_control_associations +BEGIN +SELECT + CASE + WHEN (SELECT TYPE FROM voltage_control_groups WHERE id = NEW.control_id) = 'VoltageDroopControl' + AND NOT EXISTS ( + SELECT 1 FROM entities + WHERE id = NEW.entity_id + AND entity_table IN ('thermal_generators', 'hydro_generators', 'renewable_generators') + AND entity_type <> 'RenewableNonDispatch' + ) + THEN RAISE(ABORT, 'voltage_control_associations: only generators can be members of a VoltageDroopControl') + WHEN NOT EXISTS ( + SELECT 1 FROM entities + WHERE id = NEW.entity_id + AND entity_table IN ('thermal_generators', 'hydro_generators', 'renewable_generators', + 'storage_units', 'synchronous_condensers', 'sources', 'switched_admittance', + 'facts_control_devices', 'interconnecting_converters', 'two_terminal_hvdc_lines') + AND entity_type <> 'RenewableNonDispatch' + ) + THEN RAISE(ABORT, 'voltage_control_associations: the member must be a voltage regulating device (generator, storage unit, synchronous condenser, source, switched shunt, FACTS device, converter or two-terminal VSC line)') + WHEN (NEW.terminal = 'UNDEFINED') = COALESCE( + (SELECT converter_type FROM two_terminal_hvdc_lines WHERE id = NEW.entity_id) = 'VSC', + 0 + ) + THEN RAISE(ABORT, 'voltage_control_associations.terminal names the converter of a two-terminal VSC line member and must be UNDEFINED for any other member') + END; +END; diff --git a/schema/unit_registry.sql b/schema/unit_registry.sql index 47a3bc03..4cacefcb 100644 --- a/schema/unit_registry.sql +++ b/schema/unit_registry.sql @@ -9,6 +9,7 @@ PRAGMA foreign_keys = ON; INSERT INTO quantity_kinds (name, default_unit, dimension, description) VALUES ('ActivePower', 'MW', '{"L":2,"M":1,"T":-3}', 'Active (real) power'), ('ActivePowerChangeRate', 'MW/min', '{"L":2,"M":1,"T":-4}', 'Rate of change of active power (ramp)'), + ('Admittance', 'S', '{"I":2,"L":-2,"M":-1,"T":3}', 'Admittance (complex conductance + j susceptance)'), ('Angle', 'rad', '{}', 'Angle (allowed units rad and deg)'), ('ApparentPower', 'MVA', '{"L":2,"M":1,"T":-3}', 'Apparent power'), ('CO2Emissions', 't/MMBtu', '{"Btu":-1,"M":1}', 'CO2 emission rate (mass per unit of fuel energy)'), @@ -58,6 +59,8 @@ INSERT INTO allowed_units (quantity_kind, unit) VALUES ('ActivePower', 'pu'), ('ActivePowerChangeRate', 'MW/min'), ('ActivePowerChangeRate', 'pu/min'), + ('Admittance', 'S'), + ('Admittance', 'pu'), ('Angle', 'deg'), ('Angle', 'rad'), ('ApparentPower', 'MVA'), @@ -92,6 +95,7 @@ INSERT INTO allowed_units (quantity_kind, unit) VALUES ('HeadRate', 'm/s'), ('HeatRate', 'MMBtu/MWh'), ('Impedance', 'ohm'), + ('Impedance', 'pu'), ('Inductance', 'H'), ('Length', 'km'), ('Length', 'm'), @@ -168,8 +172,6 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('attributes', 'rectifier_tap_setting', 'Dimensionless', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'LCC rectifier tap setting'), ('attributes', 'rectifier_tap_step', 'Dimensionless', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'LCC rectifier tap step'), ('attributes', 'rectifier_transformer_ratio', 'Dimensionless', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'LCC rectifier transformer ratio'), - ('attributes', 'rmpct_from', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'VSC from-converter remote-regulation share'), - ('attributes', 'rmpct_to', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'VSC to-converter remote-regulation share'), ('attributes', 'start_fuel_mmbtu_per_mw', 'StartFuelPerCapacity', 'MMBtu/MW', NULL, NULL, NULL, NULL, NULL, NULL, 'Start fuel per unit capacity; mirrors the supply_technologies column'), ('attributes', 'start_time_limits', 'OperationalDuration', 'min', NULL, NULL, NULL, NULL, NULL, NULL, 'ThermalMultiStart hot/warm/cold start time limits'), ('attributes', 'start_types', 'Dimensionless', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'ThermalMultiStart start-type count: hot, warm, cold'), @@ -187,7 +189,6 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('facts_control_devices', 'base_power', 'ApparentPower', 'MVA', NULL, NULL, NULL, NULL, NULL, NULL, 'Base power the row''s per-unit values resolve against'), ('facts_control_devices', 'max_reactive_power', 'ReactivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'Independent max reactive power ceiling, per-unit on the row''s base_power'), ('facts_control_devices', 'max_reactive_power', 'ReactivePower', 'MVAr', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Independent max reactive power ceiling in MVAr'), - ('facts_control_devices', 'regulated_bus_number', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Bus whose voltage this device regulates; 0 = local bus'), ('facts_control_devices', 'shunt_control_type', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Device class selecting the reactive-limit law (SVC vs STATCOM)'), ('facts_control_devices', 'voltage_setpoint', 'Voltage', 'pu', 'parameter_units', 'COMPONENT_BASE', NULL, NULL, NULL, 'bus->balancing_topologies.base_voltage', 'Sending-end voltage setpoint, per-unit on the bus''s base voltage'), ('facts_control_devices', 'voltage_setpoint', 'Voltage', 'kV', 'parameter_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Sending-end voltage setpoint in kV'), @@ -220,6 +221,8 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('hydro_generators', 'reactive_power_limits', 'ReactivePower', 'MVAr', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'JSON {min, max} in MVAr'), ('hydro_generators', 'time_limits', 'OperationalDuration', 'min', NULL, NULL, NULL, NULL, NULL, NULL, 'JSON {up, down}'), ('hydro_generators', 'travel_time', 'OperationalDuration', 'min', NULL, NULL, NULL, NULL, NULL, NULL, 'Water travel time'), + ('hydro_generators', 'voltage_setpoint', 'Voltage', 'pu', 'voltage_setpoint_units', 'COMPONENT_BASE', NULL, NULL, NULL, 'remote_regulated_bus_id->balancing_topologies.base_voltage', 'Voltage held at the regulated bus, per-unit on that bus''s base voltage; the regulated bus is remote_regulated_bus_id when set, else balancing_topology'), + ('hydro_generators', 'voltage_setpoint', 'Voltage', 'kV', 'voltage_setpoint_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Voltage held at the regulated bus in kV'), ('hydro_reservoirs', 'evaporative_loss', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Standing loss from evaporation as a fraction of stored volume or energy'), ('hydro_reservoirs', 'head_to_volume_factor', 'Dimensionless', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Head-to-volume conversion curve (FunctionData JSON); coefficients are dimensionless in the natural-unit convention'), ('hydro_reservoirs', 'inflow', 'ActivePower', 'MW', 'level_data_type', 'ENERGY', NULL, NULL, NULL, NULL, 'Inflow rate'), @@ -258,8 +261,6 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('interconnecting_converters', 'dc_setpoint', 'Voltage', 'pu', 'dc_control', 'DC_VOLTAGE_DROOP', 'parameter_units', 'COMPONENT_BASE', NULL, 'dc_bus->balancing_topologies.base_voltage', 'DC droop voltage setpoint, per-unit'), ('interconnecting_converters', 'dc_setpoint', 'Voltage', 'kV', 'dc_control', 'DC_VOLTAGE_DROOP', 'parameter_units', 'NATURAL_UNITS', NULL, NULL, 'DC droop voltage setpoint in kV'), ('interconnecting_converters', 'power_factor_weighting_fraction', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Reactive/active power-factor weighting fraction'), - ('interconnecting_converters', 'remote_bus_control', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Remote bus whose voltage this converter regulates; null = local bus'), - ('interconnecting_converters', 'rmpct', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Remote-bus voltage-control percentage compensation'), ('interconnecting_converters', 'voltage_limits', 'Voltage', 'pu', NULL, NULL, NULL, NULL, NULL, 'dc_bus->balancing_topologies.base_voltage', 'JSON {min, max} voltage limits'), ('loads', 'base_power', 'ApparentPower', 'MVA', NULL, NULL, NULL, NULL, NULL, NULL, 'Base power the row''s per-unit values resolve against'), ('point_to_point_bids', 'max_active_power', 'ActivePower', 'MW', NULL, NULL, NULL, NULL, NULL, NULL, 'MW envelope for the bid'), @@ -287,6 +288,8 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('renewable_generators', 'reactive_power', 'ReactivePower', 'MVAr', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Initial reactive power setpoint in MVAr'), ('renewable_generators', 'reactive_power_limits', 'ReactivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'JSON {min, max}, per-unit on the row''s base_power'), ('renewable_generators', 'reactive_power_limits', 'ReactivePower', 'MVAr', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'JSON {min, max} in MVAr'), + ('renewable_generators', 'voltage_setpoint', 'Voltage', 'pu', 'voltage_setpoint_units', 'COMPONENT_BASE', NULL, NULL, NULL, 'remote_regulated_bus_id->balancing_topologies.base_voltage', 'Voltage held at the regulated bus, per-unit on that bus''s base voltage; the regulated bus is remote_regulated_bus_id when set, else balancing_topology'), + ('renewable_generators', 'voltage_setpoint', 'Voltage', 'kV', 'voltage_setpoint_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Voltage held at the regulated bus in kV'), ('sources', 'active_power', 'ActivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'Active power injection setpoint, per-unit on the row''s base_power'), ('sources', 'active_power', 'ActivePower', 'MW', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Active power injection setpoint in MW'), ('sources', 'active_power_limits', 'ActivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'Active power limits (JSON min/max), per-unit on the row''s base_power'), @@ -309,6 +312,8 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('sources', 'reactive_power', 'ReactivePower', 'MVAr', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Reactive power injection setpoint in MVAr'), ('sources', 'reactive_power_limits', 'ReactivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'Reactive power limits (JSON min/max), per-unit on the row''s base_power'), ('sources', 'reactive_power_limits', 'ReactivePower', 'MVAr', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Reactive power limits (JSON min/max) in MVAr'), + ('sources', 'voltage_setpoint', 'Voltage', 'pu', 'voltage_setpoint_units', 'COMPONENT_BASE', NULL, NULL, NULL, 'remote_regulated_bus_id->balancing_topologies.base_voltage', 'Voltage held at the regulated bus, per-unit on that bus''s base voltage; the regulated bus is remote_regulated_bus_id when set, else bus'), + ('sources', 'voltage_setpoint', 'Voltage', 'kV', 'voltage_setpoint_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Voltage held at the regulated bus in kV'), ('sources', 'x_th', 'Reactance', 'pu', 'parameter_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', 'base_voltage', 'Thevenin reactance, per-unit on the row''s base_power'), ('sources', 'x_th', 'Reactance', 'ohm', 'parameter_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Thevenin reactance in ohm'), ('storage_technologies', 'capacity_limits_charge', 'ActivePower', 'MW', NULL, NULL, NULL, NULL, NULL, NULL, 'JSON {min, max} charge capacity limits'), @@ -365,6 +370,8 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('storage_units', 'storage_capacity', 'ElectricalEnergy', 'MWmin', 'energy_units', 'MWMIN', NULL, NULL, NULL, NULL, 'Total storage capacity, on the MWmin minutes basis'), ('storage_units', 'storage_level_limits', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'JSON {min, max} as ratio [0, 1] of storage_capacity'), ('storage_units', 'storage_target', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'End-of-simulation storage target as ratio of storage_capacity'), + ('storage_units', 'voltage_setpoint', 'Voltage', 'pu', 'voltage_setpoint_units', 'COMPONENT_BASE', NULL, NULL, NULL, 'remote_regulated_bus_id->balancing_topologies.base_voltage', 'Voltage held at the regulated bus, per-unit on that bus''s base voltage; the regulated bus is remote_regulated_bus_id when set, else balancing_topology'), + ('storage_units', 'voltage_setpoint', 'Voltage', 'kV', 'voltage_setpoint_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Voltage held at the regulated bus in kV'), ('supply_technologies', 'capacity_limits', 'ActivePower', 'MW', NULL, NULL, NULL, NULL, NULL, NULL, 'JSON {min, max} capacity limits'), ('supply_technologies', 'capital_costs', 'CostPerCapacity', 'USD/MW', NULL, NULL, NULL, NULL, NULL, NULL, 'Capital cost structure'), ('supply_technologies', 'co2', 'CO2Emissions', 't/MMBtu', NULL, NULL, NULL, NULL, NULL, NULL, 'JSON CO2 emission rates per fuel'), @@ -383,11 +390,10 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('supply_technologies', 'time_limits', 'OperationalDuration', 'min', NULL, NULL, NULL, NULL, NULL, NULL, 'JSON {up, down} time limits'), ('supply_technologies', 'unit_size', 'ActivePower', 'MW', NULL, NULL, NULL, NULL, NULL, NULL, 'Unit nameplate capacity'), ('switched_admittance', 'Y_increase', 'ReactivePower', 'MVAr', 'admittance_units', 'COMPONENT_MVAR', NULL, NULL, NULL, NULL, 'Per-step admittance increment in the PSS/E form: MVAr at 1.0 pu voltage'), - ('switched_admittance', 'Y_increase', 'Susceptance', 'S', 'admittance_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Per-step admittance increment in siemens'), + ('switched_admittance', 'Y_increase', 'Admittance', 'S', 'admittance_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Per-step admittance increment in siemens'), ('switched_admittance', 'admittance_limits', 'ReactivePower', 'MVAr', 'admittance_units', 'COMPONENT_MVAR', NULL, NULL, NULL, NULL, 'Admittance limits (JSON min/max) in the PSS/E form: MVAr at 1.0 pu voltage'), ('switched_admittance', 'admittance_limits', 'Susceptance', 'S', 'admittance_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Admittance limits (JSON min/max) in siemens'), ('switched_admittance', 'control_mode', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Switched-shunt control mode'), - ('switched_admittance', 'regulated_bus_number', 'Fraction', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Bus whose voltage/quantity this shunt regulates; 0 = local bus'), ('switched_admittance', 'solved_admittance', 'ReactivePower', 'MVAr', 'admittance_units', 'COMPONENT_MVAR', NULL, NULL, NULL, NULL, 'Solved-case admittance (PSS/E BINIT) in the PSS/E form: MVAr at 1.0 pu voltage'), ('switched_admittance', 'solved_admittance', 'Susceptance', 'S', 'admittance_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Solved-case admittance (PSS/E BINIT) in siemens'), ('synchronous_condensers', 'active_power_losses', 'ActivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'Active power loss incurred by being online, per-unit on the row''s base_power'), @@ -399,6 +405,8 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('synchronous_condensers', 'reactive_power', 'ReactivePower', 'MVAr', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Initial reactive power setpoint in MVAr'), ('synchronous_condensers', 'reactive_power_limits', 'ReactivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'Reactive power limits, JSON {min, max}, per-unit on the row''s base_power'), ('synchronous_condensers', 'reactive_power_limits', 'ReactivePower', 'MVAr', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Reactive power limits, JSON {min, max} in MVAr'), + ('synchronous_condensers', 'voltage_setpoint', 'Voltage', 'pu', 'voltage_setpoint_units', 'COMPONENT_BASE', NULL, NULL, NULL, 'remote_regulated_bus_id->balancing_topologies.base_voltage', 'Voltage held at the regulated bus, per-unit on that bus''s base voltage; the regulated bus is remote_regulated_bus_id when set, else bus'), + ('synchronous_condensers', 'voltage_setpoint', 'Voltage', 'kV', 'voltage_setpoint_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Voltage held at the regulated bus in kV'), ('thermal_generators', 'active_power', 'ActivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'Initial active power setpoint, per-unit on the row''s base_power'), ('thermal_generators', 'active_power', 'ActivePower', 'MW', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Initial active power setpoint in MW'), ('thermal_generators', 'active_power_limits', 'ActivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'JSON {min, max}, per-unit on the row''s base_power'), @@ -421,6 +429,8 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('thermal_generators', 'reactive_power_limits', 'ReactivePower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'JSON {min, max}, per-unit on the row''s base_power'), ('thermal_generators', 'reactive_power_limits', 'ReactivePower', 'MVAr', 'power_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'JSON {min, max} in MVAr'), ('thermal_generators', 'time_limits', 'OperationalDuration', 'min', NULL, NULL, NULL, NULL, NULL, NULL, 'JSON {up, down}'), + ('thermal_generators', 'voltage_setpoint', 'Voltage', 'pu', 'voltage_setpoint_units', 'COMPONENT_BASE', NULL, NULL, NULL, 'remote_regulated_bus_id->balancing_topologies.base_voltage', 'Voltage held at the regulated bus, per-unit on that bus''s base voltage; the regulated bus is remote_regulated_bus_id when set, else balancing_topology'), + ('thermal_generators', 'voltage_setpoint', 'Voltage', 'kV', 'voltage_setpoint_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Voltage held at the regulated bus in kV'), ('three_winding_transformers', 'base_power_12', 'ApparentPower', 'MVA', NULL, NULL, NULL, NULL, NULL, NULL, 'Base power the primary-secondary pair''s per-unit impedance resolves against'), ('three_winding_transformers', 'base_power_23', 'ApparentPower', 'MVA', NULL, NULL, NULL, NULL, NULL, NULL, 'Base power the secondary-tertiary pair''s per-unit impedance resolves against'), ('three_winding_transformers', 'base_power_31', 'ApparentPower', 'MVA', NULL, NULL, NULL, NULL, NULL, NULL, 'Base power the tertiary-primary pair''s per-unit impedance resolves against'), @@ -471,6 +481,10 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('transformer_circuits', 'controlled_quantity_limits', 'Voltage', 'pu', 'control_objective', 'UNDEFINED', NULL, NULL, NULL, 'base_voltage_primary', 'JSON {min, max} controlled-quantity band; no control block'), ('transformer_circuits', 'controlled_quantity_limits', 'Voltage', 'pu', 'control_objective', 'VOLTAGE', NULL, NULL, NULL, 'base_voltage_primary', 'JSON {min, max} pu voltage band (voltage control)'), ('transformer_circuits', 'controlled_quantity_limits', 'Voltage', 'pu', 'control_objective', 'VOLTAGE_DISABLED', NULL, NULL, NULL, 'base_voltage_primary', 'JSON {min, max} pu voltage band (voltage control, disabled)'), + ('transformer_circuits', 'load_drop_compensation_r', 'Resistance', 'pu', 'parameter_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', 'base_voltage_primary', 'Resistive part of the load drop compensation impedance (PSS/E CR), per-unit on the row''s base_power, referenced to base_voltage_primary'), + ('transformer_circuits', 'load_drop_compensation_r', 'Resistance', 'ohm', 'parameter_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Resistive part of the load drop compensation impedance (PSS/E CR), in ohm'), + ('transformer_circuits', 'load_drop_compensation_x', 'Reactance', 'pu', 'parameter_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', 'base_voltage_primary', 'Reactive part of the load drop compensation impedance (PSS/E CX), per-unit on the row''s base_power, referenced to base_voltage_primary'), + ('transformer_circuits', 'load_drop_compensation_x', 'Reactance', 'ohm', 'parameter_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Reactive part of the load drop compensation impedance (PSS/E CX), in ohm'), ('transformer_circuits', 'r', 'Resistance', 'pu', 'parameter_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', 'base_voltage_primary', 'Circuit resistance, per-unit on the row''s base_power, referenced to base_voltage_primary'), ('transformer_circuits', 'r', 'Resistance', 'ohm', 'parameter_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Circuit resistance, in ohm'), ('transformer_circuits', 'rating', 'ApparentPower', 'pu', 'power_units', 'COMPONENT_BASE', NULL, NULL, 'base_power', NULL, 'Thermal rating, per-unit on the row''s base_power'), @@ -524,7 +538,14 @@ INSERT INTO unit_conventions (table_name, column_name, quantity_kind, unit, disc ('virtual_participants', 'operation_cost.decremental_offer_curves', 'CostPerTime', 'USD/h', 'operation_cost.decremental_offer_curves.value_curve.curve_type', 'INPUT_OUTPUT', NULL, NULL, NULL, NULL, 'Buy offer input-output cost curve: y is a cost rate at a power level (operation_cost JSON path); payload power_units must be NATURAL_UNITS'), ('virtual_participants', 'operation_cost.incremental_offer_curves', 'CostPerEnergy', 'USD/MWh', 'operation_cost.incremental_offer_curves.value_curve.curve_type', 'AVERAGE_RATE', NULL, NULL, NULL, NULL, 'Sell offer average-rate cost curve (operation_cost JSON path); payload power_units must be NATURAL_UNITS'), ('virtual_participants', 'operation_cost.incremental_offer_curves', 'CostPerEnergy', 'USD/MWh', 'operation_cost.incremental_offer_curves.value_curve.curve_type', 'INCREMENTAL', NULL, NULL, NULL, NULL, 'Sell offer incremental (marginal) cost curve (operation_cost JSON path); payload power_units must be NATURAL_UNITS'), - ('virtual_participants', 'operation_cost.incremental_offer_curves', 'CostPerTime', 'USD/h', 'operation_cost.incremental_offer_curves.value_curve.curve_type', 'INPUT_OUTPUT', NULL, NULL, NULL, NULL, 'Sell offer input-output cost curve: y is a cost rate at a power level (operation_cost JSON path); payload power_units must be NATURAL_UNITS'); + ('virtual_participants', 'operation_cost.incremental_offer_curves', 'CostPerTime', 'USD/h', 'operation_cost.incremental_offer_curves.value_curve.curve_type', 'INPUT_OUTPUT', NULL, NULL, NULL, NULL, 'Sell offer input-output cost curve: y is a cost rate at a power level (operation_cost JSON path); payload power_units must be NATURAL_UNITS'), + ('voltage_control_associations', 'weight', 'Dimensionless', '1', NULL, NULL, NULL, NULL, NULL, NULL, 'Relative reactive power share of one voltage control group member (PSS/E RMPCT / 100)'), + ('voltage_control_groups', 'deadband_reactive_power', 'ReactivePower', 'MVAr', NULL, NULL, NULL, NULL, NULL, NULL, 'Droop controller reactive power held inside the voltage deadband (PSS/E QDB)'), + ('voltage_control_groups', 'deadband_voltage_limits', 'Voltage', 'pu', 'voltage_units', 'COMPONENT_BASE', NULL, NULL, NULL, 'regulated_bus_id->balancing_topologies.base_voltage', 'Droop controller voltage deadband (JSON min/max, PSS/E VDBLOW/VDBHIGH), per-unit on the regulated bus''s base voltage'), + ('voltage_control_groups', 'deadband_voltage_limits', 'Voltage', 'kV', 'voltage_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Droop controller voltage deadband (JSON min/max) in kV'), + ('voltage_control_groups', 'reactive_power_limits', 'ReactivePower', 'MVAr', NULL, NULL, NULL, NULL, NULL, NULL, 'Droop controller reactive power range (JSON min/max, PSS/E QMIN/QMAX)'), + ('voltage_control_groups', 'voltage_limits', 'Voltage', 'pu', 'voltage_units', 'COMPONENT_BASE', NULL, NULL, NULL, 'regulated_bus_id->balancing_topologies.base_voltage', 'Droop controller voltage range (JSON min/max, PSS/E VLOW/VHIGH), per-unit on the regulated bus''s base voltage'), + ('voltage_control_groups', 'voltage_limits', 'Voltage', 'kV', 'voltage_units', 'NATURAL_UNITS', NULL, NULL, NULL, NULL, 'Droop controller voltage range (JSON min/max) in kV'); -- 4. Per-quantity-type pu resolution rules INSERT INTO unit_basis_rules (quantity_kind, base_expression, description) VALUES @@ -532,6 +553,7 @@ INSERT INTO unit_basis_rules (quantity_kind, base_expression, description) VALUE ('ActivePowerChangeRate', 'base_power', 'Per-unit active power ramp rate: divide by the row''s base power; the /min in pu/min is a per-minute rate, not a separate time base'), ('ApparentPower', 'base_power', 'Per-unit apparent power: divide by the row''s base power'), ('Conductance', 'base_power/base_voltage^2', 'Per-unit conductance: base admittance is base_power/base_voltage^2'), + ('Impedance', 'base_voltage^2/base_power', 'Per-unit impedance: base impedance is base_voltage^2/base_power'), ('Reactance', 'base_voltage^2/base_power', 'Per-unit reactance: base impedance is base_voltage^2/base_power'), ('ReactivePower', 'base_power', 'Per-unit reactive power: divide by the row''s base power'), ('Resistance', 'base_voltage^2/base_power', 'Per-unit resistance: base impedance is base_voltage^2/base_power'), @@ -547,4 +569,4 @@ INSERT INTO unit_management_metadata (key, value, description) VALUES -- Inserting this row activates the immutability triggers. See the -- module docstring of generate_unit_registry.py for the exact repr. INSERT INTO unit_management_metadata (key, value, description) VALUES - ('unit_conventions_checksum', 'ab9137a875d33a3278f89af3ad2a0074d9aba82477a3c1c1a43ff11114b4096b', 'Registry content fingerprint -- verify with scripts/verify_unit_registry.py'); + ('unit_conventions_checksum', '80ab58bbf50508e6742a1c6a042a11de770f91f7d2038b140b1d6ffb663243c3', 'Registry content fingerprint -- verify with scripts/verify_unit_registry.py'); diff --git a/scripts/generate_unit_registry.py b/scripts/generate_unit_registry.py index 753733d6..cf539f51 100755 --- a/scripts/generate_unit_registry.py +++ b/scripts/generate_unit_registry.py @@ -63,6 +63,11 @@ "base_expression": "base_voltage^2/base_power", "description": "Per-unit reactance: base impedance is base_voltage^2/base_power", }, + { + "quantity_kind": "Impedance", + "base_expression": "base_voltage^2/base_power", + "description": "Per-unit impedance: base impedance is base_voltage^2/base_power", + }, { "quantity_kind": "Susceptance", "base_expression": "base_power/base_voltage^2", diff --git a/test/conftest.py b/test/conftest.py index b5525779..e49fc9ec 100644 --- a/test/conftest.py +++ b/test/conftest.py @@ -56,6 +56,42 @@ def make_entity( ) return entity_id +def make_bus(conn, bus_id, name): + make_entity(conn, bus_id, "balancing_topologies", "ACBus", is_topology=1) + conn.execute( + "INSERT INTO balancing_topologies(id, name) VALUES (?, ?)", (bus_id, name) + ) + return bus_id + + +def make_dc_bus(conn, bus_id, name): + """Create a DC balancing-topology entity plus its row (entity_types.is_dc = 1).""" + make_entity(conn, bus_id, "balancing_topologies", "DCBus", is_topology=1, is_dc=1) + conn.execute( + "INSERT INTO balancing_topologies(id, name) VALUES (?, ?)", (bus_id, name) + ) + return bus_id + + +def make_arc(conn, arc_id, from_id, to_id): + make_entity(conn, arc_id, "arcs", "Arc") + conn.execute( + "INSERT INTO arcs(id, from_id, to_id) VALUES (?, ?, ?)", + (arc_id, from_id, to_id), + ) + return arc_id + + +def make_circuit(conn, circuit_id, arc_id): + make_entity(conn, circuit_id, "transformer_circuits", "TransformerCircuit") + conn.execute( + "INSERT INTO transformer_circuits(id, arc_id, power_units, base_power) " + "VALUES (?, ?, 'COMPONENT_BASE', 100.0)", + (circuit_id, arc_id), + ) + return circuit_id + + # Order matters: schema (tables) -> triggers -> registry seed (+ seal) -> views. SCHEMA_FILES = [ SCHEMA_DIR / "schema.sql", diff --git a/test/test_schema_integrity.py b/test/test_schema_integrity.py index b12f154c..cb75a692 100644 --- a/test/test_schema_integrity.py +++ b/test/test_schema_integrity.py @@ -8,43 +8,15 @@ import pytest -from conftest import SCHEMAS_PATH, load_schemas_json, make_entity - - -def make_bus(conn, bus_id, name): - make_entity(conn, bus_id, "balancing_topologies", "ACBus", is_topology=1) - conn.execute( - "INSERT INTO balancing_topologies(id, name) VALUES (?, ?)", (bus_id, name) - ) - return bus_id - - -def make_dc_bus(conn, bus_id, name): - """Create a DC balancing-topology entity plus its row (entity_types.is_dc = 1).""" - make_entity(conn, bus_id, "balancing_topologies", "DCBus", is_topology=1, is_dc=1) - conn.execute( - "INSERT INTO balancing_topologies(id, name) VALUES (?, ?)", (bus_id, name) - ) - return bus_id - - -def make_arc(conn, arc_id, from_id, to_id): - make_entity(conn, arc_id, "arcs", "Arc") - conn.execute( - "INSERT INTO arcs(id, from_id, to_id) VALUES (?, ?, ?)", - (arc_id, from_id, to_id), - ) - return arc_id - - -def make_circuit(conn, circuit_id, arc_id): - make_entity(conn, circuit_id, "transformer_circuits", "TransformerCircuit") - conn.execute( - "INSERT INTO transformer_circuits(id, arc_id, power_units, base_power) " - "VALUES (?, ?, 'COMPONENT_BASE', 100.0)", - (circuit_id, arc_id), - ) - return circuit_id +from conftest import ( + SCHEMAS_PATH, + load_schemas_json, + make_arc, + make_bus, + make_circuit, + make_dc_bus, + make_entity, +) def _arc_between_new_buses(conn): @@ -271,6 +243,7 @@ def test_transformer_circuit_control_fields_roundtrip(fresh_db): ("three_winding_transformers", "shunt_location", "ThreeWindingTransformerShuntLocation"), ("transformer_circuits", "control_objective", "TransformerControlObjective"), + ("transformer_circuits", "regulated_bus_side", "TransformerRegulatedBusSide"), ] @@ -521,5 +494,3 @@ def test_dc_flag_requires_topology_type(fresh_db): fresh_db.execute( "INSERT INTO entity_types(name, is_topology, is_dc) VALUES ('Bogus', 0, 1)" ) - - diff --git a/test/test_sql_codegen.py b/test/test_sql_codegen.py index 1ef4e9bc..0a6eade8 100644 --- a/test/test_sql_codegen.py +++ b/test/test_sql_codegen.py @@ -126,7 +126,9 @@ def test_branch_parameter_pu_pairs_in_vocabulary(fresh_db): assert pairs == { ("Resistance", "pu"), ("Reactance", "pu"), + ("Impedance", "pu"), ("Susceptance", "pu"), + ("Admittance", "pu"), ("Conductance", "pu"), ("Voltage", "pu"), ("ActivePower", "pu"), @@ -314,6 +316,10 @@ def test_transformer_circuits_columns_and_units(fresh_db): ("r", "NATURAL_UNITS"): ("Resistance", "ohm"), ("x", "COMPONENT_BASE"): ("Reactance", "pu"), ("x", "NATURAL_UNITS"): ("Reactance", "ohm"), + ("load_drop_compensation_r", "COMPONENT_BASE"): ("Resistance", "pu"), + ("load_drop_compensation_r", "NATURAL_UNITS"): ("Resistance", "ohm"), + ("load_drop_compensation_x", "COMPONENT_BASE"): ("Reactance", "pu"), + ("load_drop_compensation_x", "NATURAL_UNITS"): ("Reactance", "ohm"), } assert { diff --git a/test/test_unit_registry.py b/test/test_unit_registry.py index f8336102..0a173176 100644 --- a/test/test_unit_registry.py +++ b/test/test_unit_registry.py @@ -21,9 +21,9 @@ from conftest import SCHEMA_DIR, SCRIPTS_DIR, load_schemas_json, make_entity # Expected seed row counts (current sealed state). -EXPECTED_QUANTITY_TYPES = 41 -EXPECTED_ALLOWED_UNITS = 66 -EXPECTED_UNIT_CONVENTIONS = 405 +EXPECTED_QUANTITY_TYPES = 42 +EXPECTED_ALLOWED_UNITS = 69 +EXPECTED_UNIT_CONVENTIONS = 422 VERIFY_SCRIPT = SCRIPTS_DIR / "verify_unit_registry.py" REGISTRY_SQL = SCHEMA_DIR / "unit_registry.sql" @@ -1345,7 +1345,7 @@ def test_merged_hvdc_columns_registered(db): ("dc_current", "CurrentFlow/A"), ("rating_from", "ApparentPower/MVA"), ("reactive_power_to", "ReactivePower/MVAr"), - ("rmpct_from", "Fraction/1"), + ("power_factor_weighting_fraction_from", "Fraction/1"), # Unit depends on a basis choice or a sibling control mode. A convention's # discriminator_column names a sibling *column*, which an attributes row # does not have, so these stay unregistered and each row carries its own @@ -1780,7 +1780,7 @@ def test_switched_admittance_admittance_units_conventions(db): "AND column_name IN ('Y_increase', 'solved_admittance', 'admittance_limits')" ).fetchall() assert set(rows) == { - ("Y_increase", "admittance_units", "NATURAL_UNITS", "Susceptance", "S"), + ("Y_increase", "admittance_units", "NATURAL_UNITS", "Admittance", "S"), ("Y_increase", "admittance_units", "COMPONENT_MVAR", "ReactivePower", "MVAr"), ("solved_admittance", "admittance_units", "NATURAL_UNITS", "Susceptance", "S"), ("solved_admittance", "admittance_units", "COMPONENT_MVAR", "ReactivePower", "MVAr"), diff --git a/test/test_voltage_control.py b/test/test_voltage_control.py new file mode 100644 index 00000000..8f66b7e3 --- /dev/null +++ b/test/test_voltage_control.py @@ -0,0 +1,665 @@ +"""Remote and shared voltage control: the remote regulated bus references on devices and +transformer circuits, and the voltage control groups with their memberships. + +Rule numbers follow the remote-control design: R1 (a device never names its own bus as +remote), R2 (every referenced bus or transformer exists), R4 (a transformer names a +regulated bus exactly when it regulates voltage and a side exactly when that bus is off +its arc), R7 (one group per member or converter terminal), R9 (positive weights) and R13 +(droop members are generators).""" + +import json +import sqlite3 + +import pytest + +from conftest import make_arc, make_bus, make_circuit, make_entity + +DEVICE_TABLES_WITH_REMOTE_BUS = [ + "thermal_generators", + "renewable_generators", + "hydro_generators", + "storage_units", + "synchronous_condensers", + "sources", + "switched_admittance", + "facts_control_devices", + "interconnecting_converters", +] + +GENERATOR_TABLES = [ + "thermal_generators", + "renewable_generators", + "hydro_generators", + "storage_units", + "synchronous_condensers", + "sources", +] + + +def _columns(conn, table): + return {row[1]: row for row in conn.execute(f"PRAGMA table_info({table})")} + + +def _thermal(conn, gen_id, bus_id, entity_type="ThermalStandard", **extra): + make_entity(conn, gen_id, "thermal_generators", entity_type) + conn.execute("INSERT OR IGNORE INTO prime_mover_types(name) VALUES ('ST')") + conn.execute("INSERT OR IGNORE INTO fuels(name) VALUES ('NATURAL_GAS')") + columns = { + "id": gen_id, + "name": f"gen_{gen_id}", + "prime_mover_type": "ST", + "fuel": "NATURAL_GAS", + "balancing_topology": bus_id, + "rating": 1.0, + "base_power": 100.0, + "power_units": "COMPONENT_BASE", + "active_power_limits": '{"min": 0.0, "max": 1.0}', + "status": "ONLINE", + **extra, + } + conn.execute( + f"INSERT INTO thermal_generators({', '.join(columns)}) " + f"VALUES ({', '.join('?' * len(columns))})", + list(columns.values()), + ) + return gen_id + + +def _shunt(conn, shunt_id, bus_id, **extra): + make_entity(conn, shunt_id, "switched_admittance", "SwitchedAdmittance") + columns = {"id": shunt_id, "name": f"shunt_{shunt_id}", "bus": bus_id, **extra} + conn.execute( + f"INSERT INTO switched_admittance({', '.join(columns)}) " + f"VALUES ({', '.join('?' * len(columns))})", + list(columns.values()), + ) + return shunt_id + + +def _facts(conn, facts_id, bus_id, **extra): + make_entity(conn, facts_id, "facts_control_devices", "FACTSControlDevice") + columns = { + "id": facts_id, + "name": f"facts_{facts_id}", + "bus": bus_id, + "voltage_setpoint": 1.0, + "power_units": "COMPONENT_BASE", + "base_power": 100.0, + **extra, + } + conn.execute( + f"INSERT INTO facts_control_devices({', '.join(columns)}) " + f"VALUES ({', '.join('?' * len(columns))})", + list(columns.values()), + ) + return facts_id + + +def _hvdc_line(conn, line_id, arc_id, converter_type, **extra): + make_entity(conn, line_id, "two_terminal_hvdc_lines", f"TwoTerminal{converter_type}Line") + columns = { + "id": line_id, + "name": f"dc_{line_id}", + "arc_id": arc_id, + "converter_type": converter_type, + "power_units": "COMPONENT_BASE", + "base_power": 100.0, + **extra, + } + conn.execute( + f"INSERT INTO two_terminal_hvdc_lines({', '.join(columns)}) " + f"VALUES ({', '.join('?' * len(columns))})", + list(columns.values()), + ) + return line_id + + +def _control_circuit(conn, circuit_id, arc_id, **extra): + make_entity(conn, circuit_id, "transformer_circuits", "TransformerCircuit") + columns = { + "id": circuit_id, + "arc_id": arc_id, + "power_units": "COMPONENT_BASE", + "base_power": 100.0, + **extra, + } + conn.execute( + f"INSERT INTO transformer_circuits({', '.join(columns)}) " + f"VALUES ({', '.join('?' * len(columns))})", + list(columns.values()), + ) + return circuit_id + + +def _two_winding(conn, transformer_id, circuit_id): + make_entity(conn, transformer_id, "two_winding_transformers", "TwoWindingTransformer") + conn.execute( + "INSERT INTO two_winding_transformers(id, name, circuit) VALUES (?, ?, ?)", + (transformer_id, f"xf_{transformer_id}", circuit_id), + ) + return transformer_id + + +def _sharing_group(conn, group_id, name=None): + make_entity(conn, group_id, "voltage_control_groups", "ReactivePowerSharing") + conn.execute( + "INSERT INTO voltage_control_groups(id, name, TYPE) VALUES (?, ?, 'ReactivePowerSharing')", + (group_id, name or f"share_{group_id}"), + ) + return group_id + + +def _droop_group(conn, group_id, bus_id, **extra): + make_entity(conn, group_id, "voltage_control_groups", "VoltageDroopControl") + columns = { + "id": group_id, + "name": f"droop_{group_id}", + "TYPE": "VoltageDroopControl", + "regulated_bus_id": bus_id, + "reactive_power_limits": '{"min": -50.0, "max": 50.0}', + "deadband_reactive_power": 0.0, + "voltage_units": "COMPONENT_BASE", + "deadband_voltage_limits": '{"min": 0.99, "max": 1.01}', + "voltage_limits": '{"min": 0.95, "max": 1.05}', + **extra, + } + conn.execute( + f"INSERT INTO voltage_control_groups({', '.join(columns)}) " + f"VALUES ({', '.join('?' * len(columns))})", + list(columns.values()), + ) + return group_id + + +def _member(conn, group_id, entity_id, weight=1.0, terminal="UNDEFINED"): + conn.execute( + "INSERT INTO voltage_control_associations(control_id, entity_id, weight, terminal) " + "VALUES (?, ?, ?, ?)", + (group_id, entity_id, weight, terminal), + ) + + +# Remote regulated bus references on devices + + +@pytest.mark.parametrize("table", DEVICE_TABLES_WITH_REMOTE_BUS) +def test_device_tables_reference_the_remote_regulated_bus(db, table): + """The remote bus is a nullable foreign key to the bus table; the plain bus-number + and percentage columns it replaces are gone.""" + columns = _columns(db, table) + assert "remote_regulated_bus_id" in columns + assert columns["remote_regulated_bus_id"][3] == 0, "must be nullable: null means own bus" + for legacy in ("regulated_bus_number", "remote_bus_control", "rmpct"): + assert legacy not in columns, f"{table}.{legacy} was replaced" + foreign_keys = { + row[3]: row[2] for row in db.execute(f"PRAGMA foreign_key_list({table})") + } + assert foreign_keys.get("remote_regulated_bus_id") == "balancing_topologies" + + +@pytest.mark.parametrize("table", GENERATOR_TABLES) +def test_generator_tables_carry_a_voltage_setpoint(db, table): + columns = _columns(db, table) + assert columns["voltage_setpoint"][4] == "1.0" + assert columns["voltage_setpoint_units"][4] == "'COMPONENT_BASE'" + + +def test_generator_regulates_its_own_bus_by_default(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + _thermal(fresh_db, 2, bus) + row = fresh_db.execute( + "SELECT remote_regulated_bus_id, voltage_setpoint, voltage_setpoint_units " + "FROM thermal_generators WHERE id = 2" + ).fetchone() + assert row == (None, 1.0, "COMPONENT_BASE") + + +def test_generator_remote_bus_round_trips(fresh_db): + own = make_bus(fresh_db, 1, "b1") + remote = make_bus(fresh_db, 2, "b2") + _thermal( + fresh_db, 3, own, remote_regulated_bus_id=remote, voltage_setpoint=138.0, + voltage_setpoint_units="NATURAL_UNITS", + ) + row = fresh_db.execute( + "SELECT remote_regulated_bus_id, voltage_setpoint, voltage_setpoint_units " + "FROM thermal_generators WHERE id = 3" + ).fetchone() + assert row == (remote, 138.0, "NATURAL_UNITS") + + +def test_generator_remote_bus_must_exist(fresh_db): + """R2: the remote bus is a foreign key, not a free integer.""" + bus = make_bus(fresh_db, 1, "b1") + with pytest.raises(sqlite3.IntegrityError, match="FOREIGN KEY"): + _thermal(fresh_db, 2, bus, remote_regulated_bus_id=999) + + +def test_generator_cannot_name_its_own_bus_as_remote(fresh_db): + """R1: null already means the own bus.""" + bus = make_bus(fresh_db, 1, "b1") + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _thermal(fresh_db, 2, bus, remote_regulated_bus_id=bus) + + +def test_generator_voltage_setpoint_units_is_a_voltage_basis(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _thermal(fresh_db, 2, bus, voltage_setpoint_units="SYSTEM_BASE") + + +def test_shunt_cannot_name_its_own_bus_as_remote(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + remote = make_bus(fresh_db, 2, "b2") + _shunt(fresh_db, 3, bus, remote_regulated_bus_id=remote) + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _shunt(fresh_db, 4, bus, remote_regulated_bus_id=bus) + + +def test_facts_cannot_name_its_own_bus_as_remote(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + remote = make_bus(fresh_db, 2, "b2") + _facts(fresh_db, 3, bus, remote_regulated_bus_id=remote) + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _facts(fresh_db, 4, bus, remote_regulated_bus_id=bus) + + +def test_deleting_a_droop_controllers_bus_removes_the_controller(fresh_db): + """A droop controller exists for its regulated bus the way a device exists on its bus, + so that reference cascades instead of clearing.""" + bus = make_bus(fresh_db, 1, "b1") + _droop_group(fresh_db, 10, bus) + fresh_db.execute("DELETE FROM balancing_topologies WHERE id = ?", (bus,)) + (count,) = fresh_db.execute( + "SELECT COUNT(*) FROM voltage_control_groups WHERE id = 10" + ).fetchone() + assert count == 0 + + +def test_deleting_a_remote_bus_clears_the_reference(fresh_db): + """A remote regulated bus is a non-owning reference like settlement_point_id: deleting + the bus clears it, and null already means the device regulates its own bus.""" + own = make_bus(fresh_db, 1, "b1") + remote = make_bus(fresh_db, 2, "b2") + _thermal(fresh_db, 3, own, remote_regulated_bus_id=remote) + fresh_db.execute("DELETE FROM balancing_topologies WHERE id = ?", (remote,)) + (cleared,) = fresh_db.execute( + "SELECT remote_regulated_bus_id FROM thermal_generators WHERE id = 3" + ).fetchone() + assert cleared is None + + +# Transformer circuits + + +def test_transformer_circuit_control_columns(db): + columns = _columns(db, "transformer_circuits") + assert "regulated_bus_number" not in columns + assert columns["regulated_bus_id"][3] == 0 + assert columns["regulated_bus_side"][3] == 1 + assert columns["regulated_bus_side"][4] == "'UNDEFINED'" + assert columns["load_drop_compensation_r"][4] == "0.0" + assert columns["load_drop_compensation_x"][4] == "0.0" + foreign_keys = { + row[3]: row[2] for row in db.execute("PRAGMA foreign_key_list(transformer_circuits)") + } + assert foreign_keys["regulated_bus_id"] == "balancing_topologies" + + +def test_voltage_objective_requires_a_regulated_bus(fresh_db): + """R4: a voltage-regulating circuit names its bus; a circuit that regulates no voltage + names none.""" + arc = make_arc(fresh_db, 3, make_bus(fresh_db, 1, "b1"), make_bus(fresh_db, 2, "b2")) + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _control_circuit(fresh_db, 4, arc, control_objective="VOLTAGE") + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _control_circuit(fresh_db, 5, arc, control_objective="VOLTAGE_DISABLED") + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _control_circuit(fresh_db, 6, arc, control_objective="ACTIVE_POWER_FLOW", regulated_bus_id=1) + _control_circuit(fresh_db, 7, arc, control_objective="VOLTAGE", regulated_bus_id=1) + _control_circuit(fresh_db, 8, arc, control_objective="VOLTAGE_DISABLED", regulated_bus_id=2) + _control_circuit(fresh_db, 9, arc, control_objective="ACTIVE_POWER_FLOW") + + +def test_regulated_bus_side_requires_a_regulated_bus(fresh_db): + arc = make_arc(fresh_db, 3, make_bus(fresh_db, 1, "b1"), make_bus(fresh_db, 2, "b2")) + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _control_circuit(fresh_db, 4, arc, regulated_bus_side="OPPOSITE_WINDING") + + +def test_regulated_bus_side_is_an_enum(fresh_db): + arc = make_arc(fresh_db, 3, make_bus(fresh_db, 1, "b1"), make_bus(fresh_db, 2, "b2")) + make_bus(fresh_db, 9, "far") + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _control_circuit( + fresh_db, 4, arc, control_objective="VOLTAGE", regulated_bus_id=9, + regulated_bus_side="PRIMARY", + ) + + +def test_own_bus_regulation_carries_no_side(fresh_db): + """R4: when the regulated bus is an end of the circuit's arc the side follows from + the arc, so storing one is a contradiction waiting to happen.""" + arc = make_arc(fresh_db, 3, make_bus(fresh_db, 1, "b1"), make_bus(fresh_db, 2, "b2")) + _control_circuit(fresh_db, 4, arc, control_objective="VOLTAGE", regulated_bus_id=2) + with pytest.raises(sqlite3.IntegrityError, match="regulated_bus_side"): + _control_circuit( + fresh_db, 5, arc, control_objective="VOLTAGE", regulated_bus_id=1, + regulated_bus_side="CONTROLLING_WINDING", + ) + + +def test_off_arc_regulation_requires_a_side(fresh_db): + arc = make_arc(fresh_db, 3, make_bus(fresh_db, 1, "b1"), make_bus(fresh_db, 2, "b2")) + far = make_bus(fresh_db, 9, "far") + with pytest.raises(sqlite3.IntegrityError, match="regulated_bus_side"): + _control_circuit(fresh_db, 4, arc, control_objective="VOLTAGE", regulated_bus_id=far) + _control_circuit( + fresh_db, 5, arc, control_objective="VOLTAGE", regulated_bus_id=far, + regulated_bus_side="OPPOSITE_WINDING", + ) + + +def test_regulated_bus_side_is_rechecked_on_update(fresh_db): + arc = make_arc(fresh_db, 3, make_bus(fresh_db, 1, "b1"), make_bus(fresh_db, 2, "b2")) + far = make_bus(fresh_db, 9, "far") + _control_circuit(fresh_db, 4, arc, control_objective="VOLTAGE", regulated_bus_id=2) + with pytest.raises(sqlite3.IntegrityError, match="regulated_bus_side"): + fresh_db.execute( + "UPDATE transformer_circuits SET regulated_bus_id = ? WHERE id = 4", (far,) + ) + + +def test_load_drop_compensation_round_trips_as_two_columns(fresh_db): + arc = make_arc(fresh_db, 3, make_bus(fresh_db, 1, "b1"), make_bus(fresh_db, 2, "b2")) + _control_circuit( + fresh_db, 4, arc, control_objective="VOLTAGE", regulated_bus_id=1, + load_drop_compensation_r=0.01, load_drop_compensation_x=0.02, + ) + assert fresh_db.execute( + "SELECT load_drop_compensation_r, load_drop_compensation_x " + "FROM transformer_circuits WHERE id = 4" + ).fetchone() == (0.01, 0.02) + _control_circuit(fresh_db, 5, arc, control_objective="VOLTAGE", regulated_bus_id=1) + assert fresh_db.execute( + "SELECT load_drop_compensation_r, load_drop_compensation_x " + "FROM transformer_circuits WHERE id = 5" + ).fetchone() == (0.0, 0.0) + + +# Two-terminal HVDC lines + + +def test_vsc_line_remote_buses_are_foreign_keys(db): + foreign_keys = { + row[3]: row[2] for row in db.execute("PRAGMA foreign_key_list(two_terminal_hvdc_lines)") + } + assert foreign_keys["remote_regulated_bus_id_from"] == "balancing_topologies" + assert foreign_keys["remote_regulated_bus_id_to"] == "balancing_topologies" + assert foreign_keys["rectifier_commutating_bus_id"] == "balancing_topologies" + assert foreign_keys["inverter_commutating_bus_id"] == "balancing_topologies" + assert foreign_keys["rectifier_tap_transformer_id"] == "two_winding_transformers" + assert foreign_keys["inverter_tap_transformer_id"] == "two_winding_transformers" + + +def test_vsc_converter_cannot_name_its_own_terminal_bus(fresh_db): + """R1 per terminal: the from converter's own bus is the arc's from bus.""" + b1, b2, b3 = (make_bus(fresh_db, i, f"b{i}") for i in (1, 2, 3)) + arc = make_arc(fresh_db, 4, b1, b2) + _hvdc_line(fresh_db, 5, arc, "VSC", remote_regulated_bus_id_from=b3, remote_regulated_bus_id_to=b1) + with pytest.raises(sqlite3.IntegrityError, match="remote_regulated_bus_id_from"): + _hvdc_line(fresh_db, 6, arc, "VSC", remote_regulated_bus_id_from=b1) + with pytest.raises(sqlite3.IntegrityError, match="remote_regulated_bus_id_to"): + _hvdc_line(fresh_db, 7, arc, "VSC", remote_regulated_bus_id_to=b2) + + +def test_remote_buses_belong_to_vsc_lines_only(fresh_db): + b1, b2, b3 = (make_bus(fresh_db, i, f"b{i}") for i in (1, 2, 3)) + arc = make_arc(fresh_db, 4, b1, b2) + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _hvdc_line(fresh_db, 5, arc, "LCC", remote_regulated_bus_id_from=b3) + + +def test_lcc_references_commutating_bus_and_tap_transformer(fresh_db): + b1, b2, b3, b4 = (make_bus(fresh_db, i, f"b{i}") for i in (1, 2, 3, 4)) + dc_arc = make_arc(fresh_db, 5, b1, b2) + tap_arc = make_arc(fresh_db, 6, b3, b4) + transformer = _two_winding(fresh_db, 8, make_circuit(fresh_db, 7, tap_arc)) + _hvdc_line( + fresh_db, 9, dc_arc, "LCC", rectifier_commutating_bus_id=b3, + rectifier_tap_transformer_id=transformer, + ) + row = fresh_db.execute( + "SELECT rectifier_commutating_bus_id, rectifier_tap_transformer_id, " + "inverter_commutating_bus_id, inverter_tap_transformer_id " + "FROM two_terminal_hvdc_lines WHERE id = 9" + ).fetchone() + assert row == (b3, transformer, None, None) + with pytest.raises(sqlite3.IntegrityError, match="FOREIGN KEY"): + _hvdc_line(fresh_db, 10, dc_arc, "LCC", inverter_tap_transformer_id=999) + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _hvdc_line(fresh_db, 11, dc_arc, "VSC", rectifier_commutating_bus_id=b3) + + +# Voltage control groups and memberships + + +def test_voltage_control_group_tables_exist(db): + tables = { + row[0] for row in db.execute("SELECT name FROM sqlite_master WHERE type = 'table'") + } + assert {"voltage_control_groups", "voltage_control_associations"} <= tables + + +def test_sharing_group_has_no_droop_parameters(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + _sharing_group(fresh_db, 2) + row = fresh_db.execute( + "SELECT regulated_bus_id, reactive_power_limits, voltage_limits, available " + "FROM voltage_control_groups WHERE id = 2" + ).fetchone() + assert row == (None, None, None, 1) + make_entity(fresh_db, 3, "voltage_control_groups", "ReactivePowerSharing") + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + fresh_db.execute( + "INSERT INTO voltage_control_groups(id, name, TYPE, regulated_bus_id) " + "VALUES (3, 'share_3', 'ReactivePowerSharing', ?)", + (bus,), + ) + + +def test_droop_group_carries_its_characteristic(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + _droop_group(fresh_db, 2, bus, voltage_units="NATURAL_UNITS", deadband_reactive_power=5.0) + row = fresh_db.execute( + "SELECT regulated_bus_id, deadband_reactive_power, voltage_units, " + "json_extract(voltage_limits, '$.max') FROM voltage_control_groups WHERE id = 2" + ).fetchone() + assert row == (bus, 5.0, "NATURAL_UNITS", 1.05) + + +def test_droop_group_requires_its_regulated_bus_and_limits(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _droop_group(fresh_db, 2, None) + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _droop_group(fresh_db, 3, bus, voltage_limits=None) + with pytest.raises(sqlite3.IntegrityError, match="FOREIGN KEY"): + _droop_group(fresh_db, 4, 999) + + +def test_group_type_is_one_of_the_two_attributes(fresh_db): + make_entity(fresh_db, 2, "voltage_control_groups", "PlantAttribute") + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + fresh_db.execute( + "INSERT INTO voltage_control_groups(id, name, TYPE) VALUES (2, 'x', 'PlantAttribute')" + ) + + +def test_member_weight_is_positive(fresh_db): + """R9.""" + bus = make_bus(fresh_db, 1, "b1") + gen = _thermal(fresh_db, 2, bus) + group = _sharing_group(fresh_db, 3) + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _member(fresh_db, group, gen, weight=0.0) + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _member(fresh_db, group, gen, weight=-0.5) + _member(fresh_db, group, gen, weight=0.6) + (weight,) = fresh_db.execute( + "SELECT weight FROM voltage_control_associations WHERE entity_id = ?", (gen,) + ).fetchone() + assert weight == 0.6 + + +def test_member_weight_defaults_to_one(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + gen = _thermal(fresh_db, 2, bus) + group = _sharing_group(fresh_db, 3) + fresh_db.execute( + "INSERT INTO voltage_control_associations(control_id, entity_id) VALUES (?, ?)", + (group, gen), + ) + (weight,) = fresh_db.execute( + "SELECT weight FROM voltage_control_associations WHERE entity_id = ?", (gen,) + ).fetchone() + assert weight == 1.0 + + +def test_device_belongs_to_at_most_one_group(fresh_db): + """R7 across both kinds of group.""" + bus = make_bus(fresh_db, 1, "b1") + gen = _thermal(fresh_db, 2, bus) + sharing = _sharing_group(fresh_db, 3) + droop = _droop_group(fresh_db, 4, make_bus(fresh_db, 5, "b5")) + _member(fresh_db, sharing, gen) + with pytest.raises(sqlite3.IntegrityError, match="UNIQUE"): + _member(fresh_db, droop, gen) + + +def test_each_vsc_terminal_belongs_to_at_most_one_group(fresh_db): + """R7 per converter: the two terminals of one line may sit in different groups, but + one terminal joins only once.""" + b1, b2 = make_bus(fresh_db, 1, "b1"), make_bus(fresh_db, 2, "b2") + line = _hvdc_line(fresh_db, 4, make_arc(fresh_db, 3, b1, b2), "VSC") + first, second = _sharing_group(fresh_db, 5), _sharing_group(fresh_db, 6) + _member(fresh_db, first, line, terminal="FROM") + _member(fresh_db, second, line, terminal="TO") + with pytest.raises(sqlite3.IntegrityError, match="UNIQUE"): + _member(fresh_db, second, line, terminal="FROM") + + +def test_terminal_is_never_null(fresh_db): + """UNDEFINED, not NULL, is how a single-bus member says it names no converter.""" + b1 = make_bus(fresh_db, 1, "b1") + gen = _thermal(fresh_db, 3, b1) + group = _sharing_group(fresh_db, 8) + with pytest.raises(sqlite3.IntegrityError, match="NOT NULL"): + _member(fresh_db, group, gen, terminal=None) + _member(fresh_db, group, gen) + (terminal,) = fresh_db.execute( + "SELECT terminal FROM voltage_control_associations WHERE entity_id = 3" + ).fetchone() + assert terminal == "UNDEFINED" + + +def test_terminal_names_a_vsc_converter_only(fresh_db): + b1, b2 = make_bus(fresh_db, 1, "b1"), make_bus(fresh_db, 2, "b2") + gen = _thermal(fresh_db, 3, b1) + line = _hvdc_line(fresh_db, 5, make_arc(fresh_db, 4, b1, b2), "VSC") + lcc = _hvdc_line(fresh_db, 7, make_arc(fresh_db, 6, b2, b1), "LCC") + group = _sharing_group(fresh_db, 8) + with pytest.raises(sqlite3.IntegrityError, match="terminal"): + _member(fresh_db, group, gen, terminal="FROM") + with pytest.raises(sqlite3.IntegrityError, match="terminal"): + _member(fresh_db, group, line) + with pytest.raises(sqlite3.IntegrityError, match="terminal|member"): + _member(fresh_db, group, lcc, terminal="FROM") + with pytest.raises(sqlite3.IntegrityError, match="CHECK"): + _member(fresh_db, group, line, terminal="RECTIFIER") + + +def test_sharing_members_are_voltage_control_devices(fresh_db): + """A load or a bus has no reactive power to share.""" + bus = make_bus(fresh_db, 1, "b1") + make_entity(fresh_db, 2, "loads", "PowerLoad") + group = _sharing_group(fresh_db, 3) + with pytest.raises(sqlite3.IntegrityError, match="member"): + _member(fresh_db, group, 2) + with pytest.raises(sqlite3.IntegrityError, match="member"): + _member(fresh_db, group, bus) + shunt = _shunt(fresh_db, 4, bus) + facts = _facts(fresh_db, 5, bus) + _member(fresh_db, group, shunt, weight=2.0) + _member(fresh_db, group, facts) + + +def test_droop_members_are_generators_only(fresh_db): + """R13.""" + bus = make_bus(fresh_db, 1, "b1") + droop = _droop_group(fresh_db, 2, make_bus(fresh_db, 9, "regulated")) + gen = _thermal(fresh_db, 3, bus) + shunt = _shunt(fresh_db, 4, bus) + facts = _facts(fresh_db, 5, bus) + _member(fresh_db, droop, gen, weight=0.7) + with pytest.raises(sqlite3.IntegrityError, match="generator"): + _member(fresh_db, droop, shunt) + with pytest.raises(sqlite3.IntegrityError, match="generator"): + _member(fresh_db, droop, facts) + + +def test_non_dispatch_renewable_cannot_join_a_droop_controller(fresh_db): + """The renewable table also holds RenewableNonDispatch, a fixed injection with no + voltage control, so membership is judged by the entity type, not the table.""" + bus = make_bus(fresh_db, 1, "b1") + droop = _droop_group(fresh_db, 2, make_bus(fresh_db, 9, "regulated")) + make_entity(fresh_db, 3, "renewable_generators", "RenewableNonDispatch") + make_entity(fresh_db, 4, "renewable_generators", "RenewableDispatch") + with pytest.raises(sqlite3.IntegrityError, match="generator"): + _member(fresh_db, droop, 3) + _member(fresh_db, droop, 4) + + +def test_membership_is_rechecked_on_update(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + droop = _droop_group(fresh_db, 2, make_bus(fresh_db, 9, "regulated")) + gen = _thermal(fresh_db, 3, bus) + shunt = _shunt(fresh_db, 4, bus) + _member(fresh_db, droop, gen) + with pytest.raises(sqlite3.IntegrityError, match="generator"): + fresh_db.execute( + "UPDATE voltage_control_associations SET entity_id = ? WHERE entity_id = ?", + (shunt, gen), + ) + + +def test_memberships_follow_their_group_and_member(fresh_db): + bus = make_bus(fresh_db, 1, "b1") + first = _thermal(fresh_db, 2, bus) + second = _thermal(fresh_db, 3, bus) + group = _sharing_group(fresh_db, 4) + _member(fresh_db, group, first) + _member(fresh_db, group, second) + fresh_db.execute("DELETE FROM thermal_generators WHERE id = ?", (first,)) + remaining = [ + row[0] + for row in fresh_db.execute( + "SELECT entity_id FROM voltage_control_associations ORDER BY entity_id" + ) + ] + assert remaining == [second] + fresh_db.execute("DELETE FROM voltage_control_groups WHERE id = ?", (group,)) + assert fresh_db.execute("SELECT count(*) FROM voltage_control_associations").fetchone() == (0,) + assert fresh_db.execute("SELECT count(*) FROM entities WHERE id = ?", (group,)).fetchone() == (0,) + + +def test_group_row_requires_its_entity(fresh_db): + with pytest.raises(sqlite3.IntegrityError, match="voltage_control_groups"): + fresh_db.execute( + "INSERT INTO voltage_control_groups(id, name, TYPE) VALUES (7, 'orphan', 'ReactivePowerSharing')" + ) + + +def test_schema_version_bumped_for_voltage_control(db): + (version,) = db.execute("PRAGMA user_version").fetchone() + assert version >= 2