web: Add support for transmission contingencies

dev
Alinson S. Xavier 2 weeks ago
parent 9f560df4f5
commit 5b9727b0ba

@ -27,6 +27,7 @@ import {
changeTransmissionLineData, changeTransmissionLineData,
createTransmissionLine, createTransmissionLine,
deleteTransmissionLine, deleteTransmissionLine,
rebuildContingencies,
renameTransmissionLine, renameTransmissionLine,
} from "../../core/Operations/transmissionOps"; } from "../../core/Operations/transmissionOps";
import { offerDownload } from "../Common/io"; import { offerDownload } from "../Common/io";
@ -68,6 +69,11 @@ export const TransmissionLinesColumnSpec: ColumnSpec[] = [
type: "number", type: "number",
width: 60, width: 60,
}, },
{
title: "Contingency?",
type: "lineContingency",
width: 50,
},
]; ];
const generateTransmissionLinesData = ( const generateTransmissionLinesData = (
@ -93,6 +99,7 @@ const TransmissionLinesComponent = (props: CaseBuilderSectionProps) => {
const onUpload = () => { const onUpload = () => {
fileUploadElem.current!.showFilePicker((csv: any) => { fileUploadElem.current!.showFilePicker((csv: any) => {
// Parse the CSV data
const [newLines, err] = parseCsv( const [newLines, err] = parseCsv(
csv, csv,
TransmissionLinesColumnSpec, TransmissionLinesColumnSpec,
@ -102,9 +109,19 @@ const TransmissionLinesComponent = (props: CaseBuilderSectionProps) => {
props.onError(err.message); props.onError(err.message);
return; return;
} }
// Remove contingency field from line and rebuild the contingencies section
const lineContingencies = new Set<String>();
Object.entries(newLines).forEach(([lineName, line]: [string, any]) => {
if (line["Contingency?"]) lineContingencies.add(lineName);
delete line["Contingency?"];
});
const contingencies = rebuildContingencies(lineContingencies);
const newScenario = { const newScenario = {
...props.scenario, ...props.scenario,
"Transmission lines": newLines, "Transmission lines": newLines,
Contingencies: contingencies,
}; };
props.onDataChanged(newScenario); props.onDataChanged(newScenario);
}); });

@ -18,6 +18,7 @@ import {
parseNumber, parseNumber,
} from "../../../core/Operations/commonOps"; } from "../../../core/Operations/commonOps";
import { UnitCommitmentScenario } from "../../../core/Data/types"; import { UnitCommitmentScenario } from "../../../core/Data/types";
import { getContingencyTransmissionLines } from "../../../core/Operations/transmissionOps";
export interface ColumnSpec { export interface ColumnSpec {
title: string; title: string;
@ -28,7 +29,8 @@ export interface ColumnSpec {
| "number[N]" | "number[N]"
| "number[T]" | "number[T]"
| "busRef" | "busRef"
| "boolean"; | "boolean"
| "lineContingency";
length?: number; length?: number;
width: number; width: number;
} }
@ -52,6 +54,7 @@ export const generateTableColumns = (
}); });
break; break;
case "boolean": case "boolean":
case "lineContingency":
columns.push({ columns.push({
...columnsCommonAttrs, ...columnsCommonAttrs,
title: spec.title, title: spec.title,
@ -117,6 +120,7 @@ export const generateTableData = (
): any[] => { ): any[] => {
const data: any[] = []; const data: any[] = [];
const timeslots = generateTimeslots(scenario); const timeslots = generateTimeslots(scenario);
let contingencyLines = null;
for (const [entryName, entryData] of Object.entries(container) as [ for (const [entryName, entryData] of Object.entries(container) as [
string, string,
any, any,
@ -135,6 +139,13 @@ export const generateTableData = (
case "busRef": case "busRef":
entry[spec.title] = entryData[spec.title]; entry[spec.title] = entryData[spec.title];
break; break;
case "lineContingency":
if (contingencyLines === null) {
contingencyLines = getContingencyTransmissionLines(scenario);
console.log(contingencyLines);
}
entry[spec.title] = contingencyLines.has(entryName);
break;
case "number[T]": case "number[T]":
for (let i = 0; i < timeslots.length; i++) { for (let i = 0; i < timeslots.length; i++) {
entry[`${spec.title} ${timeslots[i]}`] = entryData[spec.title][i]; entry[`${spec.title} ${timeslots[i]}`] = entryData[spec.title][i];
@ -287,12 +298,12 @@ export const parseCsv = (
} }
break; break;
} }
case "boolean": { case "boolean":
case "lineContingency":
const [val, err] = parseBool(row[spec.title]); const [val, err] = parseBool(row[spec.title]);
if (err) return [data, { message: err.message + rowRef }]; if (err) return [data, { message: err.message + rowRef }];
data[name][spec.title] = val; data[name][spec.title] = val;
break; break;
}
default: default:
throw Error(`Unknown type: ${spec.type}`); throw Error(`Unknown type: ${spec.type}`);
} }

@ -87,6 +87,12 @@ export const TEST_DATA_1: UnitCommitmentScenario = {
"Demand (MW)": [50, 50, 50, 50, 50], "Demand (MW)": [50, 50, 50, 50, 50],
}, },
}, },
Contingencies: {
l1: {
"Affected generators": [],
"Affected lines": ["l1"],
},
},
}; };
export const TEST_DATA_2: UnitCommitmentScenario = { export const TEST_DATA_2: UnitCommitmentScenario = {
@ -101,6 +107,7 @@ export const TEST_DATA_2: UnitCommitmentScenario = {
b2: { "Load (MW)": [10, 20, 30, 40] }, b2: { "Load (MW)": [10, 20, 30, 40] },
b3: { "Load (MW)": [0, 30, 0, 40] }, b3: { "Load (MW)": [0, 30, 0, 40] },
}, },
Contingencies: {},
Generators: {}, Generators: {},
"Transmission lines": {}, "Transmission lines": {},
"Storage units": {}, "Storage units": {},
@ -115,6 +122,7 @@ export const TEST_DATA_BLANK: UnitCommitmentScenario = {
"Time step (min)": 60, "Time step (min)": 60,
}, },
Buses: {}, Buses: {},
Contingencies: {},
Generators: {}, Generators: {},
"Transmission lines": {}, "Transmission lines": {},
"Storage units": {}, "Storage units": {},

@ -22,4 +22,5 @@ export const BLANK_SCENARIO: UnitCommitmentScenario = {
"Transmission lines": {}, "Transmission lines": {},
"Storage units": {}, "Storage units": {},
"Price-sensitive loads": {}, "Price-sensitive loads": {},
Contingencies: {},
}; };

@ -69,6 +69,11 @@ export interface PriceSensitiveLoad {
"Demand (MW)": number[]; "Demand (MW)": number[];
} }
export interface Contingency {
"Affected lines": string[];
"Affected generators": string[];
}
export interface UnitCommitmentScenario { export interface UnitCommitmentScenario {
Parameters: { Parameters: {
Version: string; Version: string;
@ -87,6 +92,9 @@ export interface UnitCommitmentScenario {
"Price-sensitive loads": { "Price-sensitive loads": {
[name: string]: PriceSensitiveLoad; [name: string]: PriceSensitiveLoad;
}; };
Contingencies: {
[name: string]: Contingency;
};
} }
const getTypedGenerators = <T extends any>( const getTypedGenerators = <T extends any>(

@ -7,6 +7,10 @@
import { validate, ValidationError } from "../Data/validate"; import { validate, ValidationError } from "../Data/validate";
import { UnitCommitmentScenario } from "../Data/types"; import { UnitCommitmentScenario } from "../Data/types";
import { migrate } from "../Data/migrate"; import { migrate } from "../Data/migrate";
import {
getContingencyTransmissionLines,
rebuildContingencies,
} from "./transmissionOps";
export const preprocess = ( export const preprocess = (
data: any, data: any,
@ -57,5 +61,10 @@ export const preprocess = (
} }
const scenario = result as unknown as UnitCommitmentScenario; const scenario = result as unknown as UnitCommitmentScenario;
// Rebuild contingencies
const contingencyLines = getContingencyTransmissionLines(scenario);
scenario["Contingencies"] = rebuildContingencies(contingencyLines);
return [scenario, null]; return [scenario, null];
}; };

@ -10,6 +10,8 @@ import {
changeTransmissionLineData, changeTransmissionLineData,
createTransmissionLine, createTransmissionLine,
deleteTransmissionLine, deleteTransmissionLine,
getContingencyTransmissionLines,
rebuildContingencies,
renameTransmissionLine, renameTransmissionLine,
} from "./transmissionOps"; } from "./transmissionOps";
import { ValidationError } from "../Data/validate"; import { ValidationError } from "../Data/validate";
@ -32,6 +34,12 @@ test("renameTransmissionLine", () => {
"Emergency flow limit (MW)": 20000.0, "Emergency flow limit (MW)": 20000.0,
"Flow limit penalty ($/MW)": 5000.0, "Flow limit penalty ($/MW)": 5000.0,
}); });
assert.deepEqual(newScenario["Contingencies"], {
l3: {
"Affected lines": ["l3"],
"Affected generators": [],
},
});
assert.equal(Object.keys(newScenario["Transmission lines"]).length, 1); assert.equal(Object.keys(newScenario["Transmission lines"]).length, 1);
}); });
@ -72,4 +80,23 @@ test("changeTransmissionLineData", () => {
test("deleteTransmissionLine", () => { test("deleteTransmissionLine", () => {
const newScenario = deleteTransmissionLine("l1", TEST_DATA_1); const newScenario = deleteTransmissionLine("l1", TEST_DATA_1);
assert.equal(Object.keys(newScenario["Transmission lines"]).length, 0); assert.equal(Object.keys(newScenario["Transmission lines"]).length, 0);
assert.equal(Object.keys(newScenario["Contingencies"]).length, 0);
});
test("getContingencyTransmissionLines", () => {
const contLines = getContingencyTransmissionLines(TEST_DATA_1);
assert.deepEqual(contLines, new Set(["l1"]));
});
test("rebuildContingencies", () => {
assert.deepEqual(rebuildContingencies(new Set(["l1", "l2"])), {
l1: {
"Affected lines": ["l1"],
"Affected generators": [],
},
l2: {
"Affected lines": ["l2"],
"Affected generators": [],
},
});
}); });

@ -8,11 +8,16 @@ import {
assertBusesNotEmpty, assertBusesNotEmpty,
changeData, changeData,
generateUniqueName, generateUniqueName,
parseBool,
renameItemInObject, renameItemInObject,
} from "./commonOps"; } from "./commonOps";
import { ValidationError } from "../Data/validate"; import { ValidationError } from "../Data/validate";
import { TransmissionLinesColumnSpec } from "../../components/CaseBuilder/TransmissionLines"; import { TransmissionLinesColumnSpec } from "../../components/CaseBuilder/TransmissionLines";
import { TransmissionLine, UnitCommitmentScenario } from "../Data/types"; import {
Contingency,
TransmissionLine,
UnitCommitmentScenario,
} from "../Data/types";
export const createTransmissionLine = ( export const createTransmissionLine = (
scenario: UnitCommitmentScenario, scenario: UnitCommitmentScenario,
@ -51,7 +56,24 @@ export const renameTransmissionLine = (
scenario["Transmission lines"], scenario["Transmission lines"],
); );
if (err) return [scenario, err]; if (err) return [scenario, err];
return [{ ...scenario, "Transmission lines": newLine }, null];
// Update transmission line contingencies
let newContingencies = scenario["Contingencies"];
const contingencyLines = getContingencyTransmissionLines(scenario);
if (contingencyLines.has(oldName)) {
contingencyLines.delete(oldName);
contingencyLines.add(newName);
newContingencies = rebuildContingencies(contingencyLines);
}
return [
{
...scenario,
"Transmission lines": newLine,
Contingencies: newContingencies,
},
null,
];
}; };
export const changeTransmissionLineData = ( export const changeTransmissionLineData = (
@ -60,24 +82,38 @@ export const changeTransmissionLineData = (
newValueStr: string, newValueStr: string,
scenario: UnitCommitmentScenario, scenario: UnitCommitmentScenario,
): [UnitCommitmentScenario, ValidationError | null] => { ): [UnitCommitmentScenario, ValidationError | null] => {
const [newLine, err] = changeData( if (field === "Contingency?") {
field, // Parse boolean value
newValueStr, const [newValue, err] = parseBool(newValueStr);
scenario["Transmission lines"][line]!, if (err) return [scenario, err];
TransmissionLinesColumnSpec,
scenario, // Rebuild contingencies
); const contLines = getContingencyTransmissionLines(scenario);
if (err) return [scenario, err]; if (newValue) contLines.add(line);
return [ else contLines.delete(line);
{ const newContingencies = rebuildContingencies(contLines);
...scenario,
"Transmission lines": { return [{ ...scenario, Contingencies: newContingencies }, null];
...scenario["Transmission lines"], } else {
[line]: newLine as TransmissionLine, const [newLine, err] = changeData(
field,
newValueStr,
scenario["Transmission lines"][line]!,
TransmissionLinesColumnSpec,
scenario,
);
if (err) return [scenario, err];
return [
{
...scenario,
"Transmission lines": {
...scenario["Transmission lines"],
[line]: newLine as TransmissionLine,
},
}, },
}, null,
null, ];
]; }
}; };
export const deleteTransmissionLine = ( export const deleteTransmissionLine = (
@ -85,5 +121,43 @@ export const deleteTransmissionLine = (
scenario: UnitCommitmentScenario, scenario: UnitCommitmentScenario,
): UnitCommitmentScenario => { ): UnitCommitmentScenario => {
const { [name]: _, ...newLines } = scenario["Transmission lines"]; const { [name]: _, ...newLines } = scenario["Transmission lines"];
return { ...scenario, "Transmission lines": newLines };
// Update transmission line contingencies
let newContingencies = scenario["Contingencies"];
const contingencyLines = getContingencyTransmissionLines(scenario);
if (contingencyLines.has(name)) {
contingencyLines.delete(name);
newContingencies = rebuildContingencies(contingencyLines);
}
return {
...scenario,
"Transmission lines": newLines,
Contingencies: newContingencies,
};
};
export const getContingencyTransmissionLines = (
scenario: UnitCommitmentScenario,
): Set<String> => {
let result: Set<String> = new Set();
Object.entries(scenario.Contingencies).forEach(([name, contingency]) => {
if (contingency["Affected lines"].length !== 1)
throw Error("not implemented");
result.add(contingency["Affected lines"][0]!!);
});
return result;
};
export const rebuildContingencies = (
contingencyLines: Set<String>,
): { [name: string]: Contingency } => {
const result: { [name: string]: Contingency } = {};
contingencyLines.forEach((lineName) => {
result[lineName as string] = {
"Affected lines": [lineName as string],
"Affected generators": [],
};
});
return result;
}; };

Loading…
Cancel
Save