mirror of
https://github.com/ANL-CEEESA/UnitCommitment.jl.git
synced 2025-12-06 00:08:52 -06:00
web: Start implementation of ThermalUnitsComponent
This commit is contained in:
@@ -31,16 +31,17 @@ import {
|
|||||||
deleteBus,
|
deleteBus,
|
||||||
renameBus,
|
renameBus,
|
||||||
} from "../../core/Operations/busOps";
|
} from "../../core/Operations/busOps";
|
||||||
|
import { CaseBuilderSectionProps } from "./CaseBuilder";
|
||||||
|
|
||||||
export const BusesColumnSpec: ColumnSpec[] = [
|
export const BusesColumnSpec: ColumnSpec[] = [
|
||||||
{
|
{
|
||||||
title: "Name",
|
title: "Name",
|
||||||
type: "string",
|
type: "string",
|
||||||
width: 150,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Load (MW)",
|
title: "Load (MW)",
|
||||||
type: "number[]",
|
type: "number[T]",
|
||||||
width: 60,
|
width: 60,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -52,13 +53,8 @@ export const generateBusesData = (
|
|||||||
const data = generateTableData(scenario.Buses, BusesColumnSpec, scenario);
|
const data = generateTableData(scenario.Buses, BusesColumnSpec, scenario);
|
||||||
return [data, columns];
|
return [data, columns];
|
||||||
};
|
};
|
||||||
interface BusesProps {
|
|
||||||
scenario: UnitCommitmentScenario;
|
|
||||||
onDataChanged: (scenario: UnitCommitmentScenario) => void;
|
|
||||||
onError: (msg: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function BusesComponent(props: BusesProps) {
|
function BusesComponent(props: CaseBuilderSectionProps) {
|
||||||
const fileUploadElem = useRef<FileUploadElement>(null);
|
const fileUploadElem = useRef<FileUploadElement>(null);
|
||||||
|
|
||||||
const onDownload = () => {
|
const onDownload = () => {
|
||||||
|
|||||||
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
import Header from "./Header";
|
import Header from "./Header";
|
||||||
import Parameters from "./Parameters";
|
import Parameters from "./Parameters";
|
||||||
import Buses from "./Buses";
|
import BusesComponent from "./Buses";
|
||||||
import {
|
import {
|
||||||
BLANK_SCENARIO,
|
BLANK_SCENARIO,
|
||||||
TEST_SCENARIO,
|
TEST_SCENARIO,
|
||||||
@@ -22,6 +22,13 @@ import { offerDownload } from "../Common/io";
|
|||||||
import { preprocess } from "../../core/Operations/preprocessing";
|
import { preprocess } from "../../core/Operations/preprocessing";
|
||||||
import Toast from "../Common/Forms/Toast";
|
import Toast from "../Common/Forms/Toast";
|
||||||
import ProfiledUnitsComponent from "./ProfiledUnits";
|
import ProfiledUnitsComponent from "./ProfiledUnits";
|
||||||
|
import ThermalUnitsComponent from "./ThermalUnits";
|
||||||
|
|
||||||
|
export interface CaseBuilderSectionProps {
|
||||||
|
scenario: UnitCommitmentScenario;
|
||||||
|
onDataChanged: (scenario: UnitCommitmentScenario) => void;
|
||||||
|
onError: (msg: string) => void;
|
||||||
|
}
|
||||||
|
|
||||||
const CaseBuilder = () => {
|
const CaseBuilder = () => {
|
||||||
const [scenario, setScenario] = useState(() => {
|
const [scenario, setScenario] = useState(() => {
|
||||||
@@ -99,7 +106,12 @@ const CaseBuilder = () => {
|
|||||||
onDataChanged={onDataChanged}
|
onDataChanged={onDataChanged}
|
||||||
onError={setToastMessage}
|
onError={setToastMessage}
|
||||||
/>
|
/>
|
||||||
<Buses
|
<BusesComponent
|
||||||
|
scenario={scenario}
|
||||||
|
onDataChanged={onDataChanged}
|
||||||
|
onError={setToastMessage}
|
||||||
|
/>
|
||||||
|
<ThermalUnitsComponent
|
||||||
scenario={scenario}
|
scenario={scenario}
|
||||||
onDataChanged={onDataChanged}
|
onDataChanged={onDataChanged}
|
||||||
onError={setToastMessage}
|
onError={setToastMessage}
|
||||||
|
|||||||
@@ -18,7 +18,10 @@ import DataTable, {
|
|||||||
generateTableData,
|
generateTableData,
|
||||||
parseCsv,
|
parseCsv,
|
||||||
} from "../Common/Forms/DataTable";
|
} from "../Common/Forms/DataTable";
|
||||||
import { UnitCommitmentScenario } from "../../core/fixtures";
|
import {
|
||||||
|
getProfiledGenerators,
|
||||||
|
UnitCommitmentScenario,
|
||||||
|
} from "../../core/fixtures";
|
||||||
import { ColumnDefinition } from "tabulator-tables";
|
import { ColumnDefinition } from "tabulator-tables";
|
||||||
import { offerDownload } from "../Common/io";
|
import { offerDownload } from "../Common/io";
|
||||||
import FileUploadElement from "../Common/Buttons/FileUploadElement";
|
import FileUploadElement from "../Common/Buttons/FileUploadElement";
|
||||||
@@ -30,23 +33,18 @@ import {
|
|||||||
renameGenerator,
|
renameGenerator,
|
||||||
} from "../../core/Operations/generatorOps";
|
} from "../../core/Operations/generatorOps";
|
||||||
import { ValidationError } from "../../core/Validation/validate";
|
import { ValidationError } from "../../core/Validation/validate";
|
||||||
|
import { CaseBuilderSectionProps } from "./CaseBuilder";
|
||||||
interface ProfiledUnitsProps {
|
|
||||||
scenario: UnitCommitmentScenario;
|
|
||||||
onDataChanged: (scenario: UnitCommitmentScenario) => void;
|
|
||||||
onError: (msg: string) => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const ProfiledUnitsColumnSpec: ColumnSpec[] = [
|
export const ProfiledUnitsColumnSpec: ColumnSpec[] = [
|
||||||
{
|
{
|
||||||
title: "Name",
|
title: "Name",
|
||||||
type: "string",
|
type: "string",
|
||||||
width: 150,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Bus",
|
title: "Bus",
|
||||||
type: "busRef",
|
type: "busRef",
|
||||||
width: 150,
|
width: 100,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Cost ($/MW)",
|
title: "Cost ($/MW)",
|
||||||
@@ -55,12 +53,12 @@ export const ProfiledUnitsColumnSpec: ColumnSpec[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Maximum power (MW)",
|
title: "Maximum power (MW)",
|
||||||
type: "number[]",
|
type: "number[T]",
|
||||||
width: 60,
|
width: 60,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: "Minimum power (MW)",
|
title: "Minimum power (MW)",
|
||||||
type: "number[]",
|
type: "number[T]",
|
||||||
width: 60,
|
width: 60,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
@@ -70,14 +68,14 @@ const generateProfiledUnitsData = (
|
|||||||
): [any[], ColumnDefinition[]] => {
|
): [any[], ColumnDefinition[]] => {
|
||||||
const columns = generateTableColumns(scenario, ProfiledUnitsColumnSpec);
|
const columns = generateTableColumns(scenario, ProfiledUnitsColumnSpec);
|
||||||
const data = generateTableData(
|
const data = generateTableData(
|
||||||
scenario.Generators,
|
getProfiledGenerators(scenario),
|
||||||
ProfiledUnitsColumnSpec,
|
ProfiledUnitsColumnSpec,
|
||||||
scenario,
|
scenario,
|
||||||
);
|
);
|
||||||
return [data, columns];
|
return [data, columns];
|
||||||
};
|
};
|
||||||
|
|
||||||
const ProfiledUnitsComponent = (props: ProfiledUnitsProps) => {
|
const ProfiledUnitsComponent = (props: CaseBuilderSectionProps) => {
|
||||||
const fileUploadElem = useRef<FileUploadElement>(null);
|
const fileUploadElem = useRef<FileUploadElement>(null);
|
||||||
|
|
||||||
const onDownload = () => {
|
const onDownload = () => {
|
||||||
|
|||||||
228
web/src/components/CaseBuilder/ThermalUnits.tsx
Normal file
228
web/src/components/CaseBuilder/ThermalUnits.tsx
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
|
||||||
|
* Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
|
||||||
|
* Released under the modified BSD license. See COPYING.md for more details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import DataTable, {
|
||||||
|
ColumnSpec,
|
||||||
|
generateTableColumns,
|
||||||
|
generateTableData,
|
||||||
|
} from "../Common/Forms/DataTable";
|
||||||
|
import { CaseBuilderSectionProps } from "./CaseBuilder";
|
||||||
|
import { useRef } from "react";
|
||||||
|
import FileUploadElement from "../Common/Buttons/FileUploadElement";
|
||||||
|
import { ValidationError } from "../../core/Validation/validate";
|
||||||
|
import SectionHeader from "../Common/SectionHeader/SectionHeader";
|
||||||
|
import SectionButton from "../Common/Buttons/SectionButton";
|
||||||
|
import {
|
||||||
|
faDownload,
|
||||||
|
faPlus,
|
||||||
|
faUpload,
|
||||||
|
} from "@fortawesome/free-solid-svg-icons";
|
||||||
|
import {
|
||||||
|
getThermalGenerators,
|
||||||
|
UnitCommitmentScenario,
|
||||||
|
} from "../../core/fixtures";
|
||||||
|
import { ColumnDefinition } from "tabulator-tables";
|
||||||
|
|
||||||
|
export const ThermalUnitsColumnSpec: ColumnSpec[] = [
|
||||||
|
{
|
||||||
|
title: "Name",
|
||||||
|
type: "string",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Bus",
|
||||||
|
type: "busRef",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Production cost curve (MW)",
|
||||||
|
type: "number[N]",
|
||||||
|
length: 10,
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Production cost curve ($)",
|
||||||
|
type: "number[N]",
|
||||||
|
length: 10,
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Startup costs ($)",
|
||||||
|
type: "number[N]",
|
||||||
|
length: 5,
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Startup delays (h)",
|
||||||
|
type: "number[N]",
|
||||||
|
length: 5,
|
||||||
|
width: 60,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Min uptime (h)",
|
||||||
|
type: "number",
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Min downtime (h)",
|
||||||
|
type: "number",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Ramp up limit (MW)",
|
||||||
|
type: "number",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Ramp down limit (MW)",
|
||||||
|
type: "number",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Startup limit (MW)",
|
||||||
|
type: "number",
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Shutdown limit (MW)",
|
||||||
|
type: "number",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Initial status (h)",
|
||||||
|
type: "number",
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Initial power (MW)",
|
||||||
|
type: "number",
|
||||||
|
width: 100,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: "Must run?",
|
||||||
|
type: "boolean",
|
||||||
|
width: 80,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
const generateThermalUnitsData = (
|
||||||
|
scenario: UnitCommitmentScenario,
|
||||||
|
): [any[], ColumnDefinition[]] => {
|
||||||
|
const columns = generateTableColumns(scenario, ThermalUnitsColumnSpec);
|
||||||
|
const data = generateTableData(
|
||||||
|
getThermalGenerators(scenario),
|
||||||
|
ThermalUnitsColumnSpec,
|
||||||
|
scenario,
|
||||||
|
);
|
||||||
|
return [data, columns];
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThermalUnitsComponent = (props: CaseBuilderSectionProps) => {
|
||||||
|
const fileUploadElem = useRef<FileUploadElement>(null);
|
||||||
|
|
||||||
|
const onDownload = () => {
|
||||||
|
// const [data, columns] = generateThermalUnitsData(props.scenario);
|
||||||
|
// const csvContents = generateCsv(data, columns);
|
||||||
|
// offerDownload(csvContents, "text/csv", "profiled_units.csv");
|
||||||
|
};
|
||||||
|
|
||||||
|
const onUpload = () => {
|
||||||
|
// fileUploadElem.current!.showFilePicker((csvContents: any) => {
|
||||||
|
// const [newGenerators, err] = parseCsv(
|
||||||
|
// csvContents,
|
||||||
|
// ThermalUnitsColumnSpec,
|
||||||
|
// props.scenario,
|
||||||
|
// );
|
||||||
|
// if (err) {
|
||||||
|
// props.onError(err.message);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// for (const gen in newGenerators) {
|
||||||
|
// newGenerators[gen]["Type"] = "Thermal";
|
||||||
|
// }
|
||||||
|
//
|
||||||
|
// const newScenario = {
|
||||||
|
// ...props.scenario,
|
||||||
|
// Generators: newGenerators,
|
||||||
|
// };
|
||||||
|
// props.onDataChanged(newScenario);
|
||||||
|
// });
|
||||||
|
};
|
||||||
|
|
||||||
|
const onAdd = () => {
|
||||||
|
// const [newScenario, err] = createThermalUnit(props.scenario);
|
||||||
|
// if (err) {
|
||||||
|
// props.onError(err.message);
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
// props.onDataChanged(newScenario);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDelete = (name: string): ValidationError | null => {
|
||||||
|
// const newScenario = deleteGenerator(name, props.scenario);
|
||||||
|
// props.onDataChanged(newScenario);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onDataChanged = (
|
||||||
|
name: string,
|
||||||
|
field: string,
|
||||||
|
newValue: string,
|
||||||
|
): ValidationError | null => {
|
||||||
|
// const [newScenario, err] = changeThermalUnitData(
|
||||||
|
// name,
|
||||||
|
// field,
|
||||||
|
// newValue,
|
||||||
|
// props.scenario,
|
||||||
|
// );
|
||||||
|
// if (err) {
|
||||||
|
// props.onError(err.message);
|
||||||
|
// return err;
|
||||||
|
// }
|
||||||
|
// props.onDataChanged(newScenario);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRename = (
|
||||||
|
oldName: string,
|
||||||
|
newName: string,
|
||||||
|
): ValidationError | null => {
|
||||||
|
// const [newScenario, err] = renameGenerator(
|
||||||
|
// oldName,
|
||||||
|
// newName,
|
||||||
|
// props.scenario,
|
||||||
|
// );
|
||||||
|
// if (err) {
|
||||||
|
// props.onError(err.message);
|
||||||
|
// return err;
|
||||||
|
// }
|
||||||
|
// props.onDataChanged(newScenario);
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<SectionHeader title="Thermal Units">
|
||||||
|
<SectionButton icon={faPlus} tooltip="Add" onClick={onAdd} />
|
||||||
|
<SectionButton
|
||||||
|
icon={faDownload}
|
||||||
|
tooltip="Download"
|
||||||
|
onClick={onDownload}
|
||||||
|
/>
|
||||||
|
<SectionButton icon={faUpload} tooltip="Upload" onClick={onUpload} />
|
||||||
|
</SectionHeader>
|
||||||
|
<DataTable
|
||||||
|
onRowDeleted={onDelete}
|
||||||
|
onRowRenamed={onRename}
|
||||||
|
onDataChanged={onDataChanged}
|
||||||
|
generateData={() => generateThermalUnitsData(props.scenario)}
|
||||||
|
/>
|
||||||
|
<FileUploadElement ref={fileUploadElem} accept=".csv" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThermalUnitsComponent;
|
||||||
@@ -6,9 +6,71 @@
|
|||||||
|
|
||||||
import assert from "node:assert";
|
import assert from "node:assert";
|
||||||
import { BusesColumnSpec, generateBusesData } from "../../CaseBuilder/Buses";
|
import { BusesColumnSpec, generateBusesData } from "../../CaseBuilder/Buses";
|
||||||
import { generateCsv, parseCsv } from "./DataTable";
|
import {
|
||||||
|
floatFormatter,
|
||||||
|
generateCsv,
|
||||||
|
generateTableColumns,
|
||||||
|
parseCsv,
|
||||||
|
} from "./DataTable";
|
||||||
import { TEST_DATA_1 } from "../../../core/fixtures.test";
|
import { TEST_DATA_1 } from "../../../core/fixtures.test";
|
||||||
import { ProfiledUnitsColumnSpec } from "../../CaseBuilder/ProfiledUnits";
|
import { ProfiledUnitsColumnSpec } from "../../CaseBuilder/ProfiledUnits";
|
||||||
|
import { ThermalUnitsColumnSpec } from "../../CaseBuilder/ThermalUnits";
|
||||||
|
|
||||||
|
test("generateTableColumns (ProfiledUnits)", () => {
|
||||||
|
const columns = generateTableColumns(TEST_DATA_1, ProfiledUnitsColumnSpec);
|
||||||
|
assert.equal(columns.length, 5);
|
||||||
|
assert.deepEqual(columns[0], {
|
||||||
|
editor: "input",
|
||||||
|
editorParams: {
|
||||||
|
selectContents: true,
|
||||||
|
},
|
||||||
|
field: "Name",
|
||||||
|
formatter: "plaintext",
|
||||||
|
headerHozAlign: "left",
|
||||||
|
headerSort: false,
|
||||||
|
headerWordWrap: true,
|
||||||
|
hozAlign: "left",
|
||||||
|
minWidth: 100,
|
||||||
|
resizable: false,
|
||||||
|
title: "Name",
|
||||||
|
});
|
||||||
|
assert.equal(columns[3]!["columns"]!.length, 5);
|
||||||
|
assert.deepEqual(columns[3]!["columns"]![0], {
|
||||||
|
editor: "input",
|
||||||
|
editorParams: {
|
||||||
|
selectContents: true,
|
||||||
|
},
|
||||||
|
field: "Maximum power (MW) 00:00",
|
||||||
|
formatter: floatFormatter,
|
||||||
|
headerHozAlign: "left",
|
||||||
|
headerSort: false,
|
||||||
|
headerWordWrap: true,
|
||||||
|
hozAlign: "left",
|
||||||
|
minWidth: 60,
|
||||||
|
resizable: false,
|
||||||
|
title: "00:00",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
test("generateTableColumns (ThermalUnits)", () => {
|
||||||
|
const columns = generateTableColumns(TEST_DATA_1, ThermalUnitsColumnSpec);
|
||||||
|
assert.equal(columns[2]!["columns"]!.length, 10);
|
||||||
|
assert.deepEqual(columns[2]!["columns"]![0], {
|
||||||
|
editor: "input",
|
||||||
|
editorParams: {
|
||||||
|
selectContents: true,
|
||||||
|
},
|
||||||
|
field: "Production cost curve (MW) 1",
|
||||||
|
formatter: floatFormatter,
|
||||||
|
headerHozAlign: "left",
|
||||||
|
headerSort: false,
|
||||||
|
headerWordWrap: true,
|
||||||
|
hozAlign: "left",
|
||||||
|
minWidth: 60,
|
||||||
|
resizable: false,
|
||||||
|
title: "1",
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
test("generate CSV", () => {
|
test("generate CSV", () => {
|
||||||
const [data, columns] = generateBusesData(TEST_DATA_1);
|
const [data, columns] = generateBusesData(TEST_DATA_1);
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import { parseNumber } from "../../../core/Operations/commonOps";
|
|||||||
|
|
||||||
export interface ColumnSpec {
|
export interface ColumnSpec {
|
||||||
title: string;
|
title: string;
|
||||||
type: "string" | "number" | "number[]" | "busRef";
|
type: "string" | "number" | "number[N]" | "number[T]" | "busRef" | "boolean";
|
||||||
|
length?: number;
|
||||||
width: number;
|
width: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -28,8 +29,10 @@ export const generateTableColumns = (
|
|||||||
const timeSlots = generateTimeslots(scenario);
|
const timeSlots = generateTimeslots(scenario);
|
||||||
const columns: ColumnDefinition[] = [];
|
const columns: ColumnDefinition[] = [];
|
||||||
colSpecs.forEach((spec) => {
|
colSpecs.forEach((spec) => {
|
||||||
|
const subColumns: ColumnDefinition[] = [];
|
||||||
switch (spec.type) {
|
switch (spec.type) {
|
||||||
case "string":
|
case "string":
|
||||||
|
case "boolean":
|
||||||
case "busRef":
|
case "busRef":
|
||||||
columns.push({
|
columns.push({
|
||||||
...columnsCommonAttrs,
|
...columnsCommonAttrs,
|
||||||
@@ -47,8 +50,7 @@ export const generateTableColumns = (
|
|||||||
formatter: floatFormatter,
|
formatter: floatFormatter,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
case "number[]":
|
case "number[T]":
|
||||||
const subColumns: ColumnDefinition[] = [];
|
|
||||||
timeSlots.forEach((t) => {
|
timeSlots.forEach((t) => {
|
||||||
subColumns.push({
|
subColumns.push({
|
||||||
...columnsCommonAttrs,
|
...columnsCommonAttrs,
|
||||||
@@ -63,6 +65,21 @@ export const generateTableColumns = (
|
|||||||
columns: subColumns,
|
columns: subColumns,
|
||||||
});
|
});
|
||||||
break;
|
break;
|
||||||
|
case "number[N]":
|
||||||
|
for (let i = 1; i <= spec.length!; i++) {
|
||||||
|
subColumns.push({
|
||||||
|
...columnsCommonAttrs,
|
||||||
|
title: `${i}`,
|
||||||
|
field: `${spec.title} ${i}`,
|
||||||
|
minWidth: spec.width,
|
||||||
|
formatter: floatFormatter,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
columns.push({
|
||||||
|
title: spec.title,
|
||||||
|
columns: subColumns,
|
||||||
|
});
|
||||||
|
break;
|
||||||
default:
|
default:
|
||||||
throw Error(`Unknown type: ${spec.type}`);
|
throw Error(`Unknown type: ${spec.type}`);
|
||||||
}
|
}
|
||||||
@@ -93,7 +110,7 @@ export const generateTableData = (
|
|||||||
case "busRef":
|
case "busRef":
|
||||||
entry[spec.title] = entryData[spec.title];
|
entry[spec.title] = entryData[spec.title];
|
||||||
break;
|
break;
|
||||||
case "number[]":
|
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];
|
||||||
}
|
}
|
||||||
@@ -207,7 +224,7 @@ export const parseCsv = (
|
|||||||
}
|
}
|
||||||
data[name][spec.title] = row[spec.title];
|
data[name][spec.title] = row[spec.title];
|
||||||
break;
|
break;
|
||||||
case "number[]":
|
case "number[T]":
|
||||||
data[name][spec.title] = Array(timeslots.length);
|
data[name][spec.title] = Array(timeslots.length);
|
||||||
|
|
||||||
for (let i = 0; i < timeslots.length; i++) {
|
for (let i = 0; i < timeslots.length; i++) {
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ export const changeData = (
|
|||||||
return changeBusRefData(fieldName, newValueStr, container, scenario);
|
return changeBusRefData(fieldName, newValueStr, container, scenario);
|
||||||
case "number":
|
case "number":
|
||||||
return changeNumberData(fieldName, newValueStr, container);
|
return changeNumberData(fieldName, newValueStr, container);
|
||||||
case "number[]":
|
case "number[T]":
|
||||||
return changeNumberVecData(
|
return changeNumberVecData(
|
||||||
fieldName,
|
fieldName,
|
||||||
fieldTime!,
|
fieldTime!,
|
||||||
|
|||||||
@@ -16,28 +16,13 @@ import {
|
|||||||
test("createProfiledUnit", () => {
|
test("createProfiledUnit", () => {
|
||||||
const [newScenario, err] = createProfiledUnit(TEST_DATA_1);
|
const [newScenario, err] = createProfiledUnit(TEST_DATA_1);
|
||||||
assert(err === null);
|
assert(err === null);
|
||||||
assert.deepEqual(newScenario.Generators, {
|
assert.equal(Object.keys(newScenario.Generators).length, 4);
|
||||||
pu1: {
|
assert.deepEqual(newScenario.Generators["pu3"], {
|
||||||
Bus: "b1",
|
Bus: "b1",
|
||||||
Type: "Profiled",
|
Type: "Profiled",
|
||||||
"Cost ($/MW)": 12.5,
|
"Cost ($/MW)": 0,
|
||||||
"Maximum power (MW)": [10, 12, 13, 15, 20],
|
"Maximum power (MW)": [0, 0, 0, 0, 0],
|
||||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
||||||
},
|
|
||||||
pu2: {
|
|
||||||
Bus: "b1",
|
|
||||||
Type: "Profiled",
|
|
||||||
"Cost ($/MW)": 120,
|
|
||||||
"Maximum power (MW)": [50, 50, 50, 50, 50],
|
|
||||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
|
||||||
},
|
|
||||||
pu3: {
|
|
||||||
Bus: "b1",
|
|
||||||
Type: "Profiled",
|
|
||||||
"Cost ($/MW)": 0,
|
|
||||||
"Maximum power (MW)": [0, 0, 0, 0, 0],
|
|
||||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -66,21 +51,12 @@ test("changeProfiledUnitData", () => {
|
|||||||
assert.equal(err, null);
|
assert.equal(err, null);
|
||||||
[scenario, err] = changeProfiledUnitData("pu2", "Bus", "b3", scenario);
|
[scenario, err] = changeProfiledUnitData("pu2", "Bus", "b3", scenario);
|
||||||
assert.equal(err, null);
|
assert.equal(err, null);
|
||||||
assert.deepEqual(scenario.Generators, {
|
assert.deepEqual(scenario.Generators["pu2"], {
|
||||||
pu1: {
|
Bus: "b3",
|
||||||
Bus: "b1",
|
Type: "Profiled",
|
||||||
Type: "Profiled",
|
"Cost ($/MW)": 120,
|
||||||
"Cost ($/MW)": 99,
|
"Maximum power (MW)": [50, 50, 50, 50, 50],
|
||||||
"Maximum power (MW)": [10, 12, 13, 99, 20],
|
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
||||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
|
||||||
},
|
|
||||||
pu2: {
|
|
||||||
Bus: "b3",
|
|
||||||
Type: "Profiled",
|
|
||||||
"Cost ($/MW)": 120,
|
|
||||||
"Maximum power (MW)": [50, 50, 50, 50, 50],
|
|
||||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -94,34 +70,26 @@ test("changeProfiledUnitData with invalid bus", () => {
|
|||||||
|
|
||||||
test("deleteGenerator", () => {
|
test("deleteGenerator", () => {
|
||||||
const newScenario = deleteGenerator("pu1", TEST_DATA_1);
|
const newScenario = deleteGenerator("pu1", TEST_DATA_1);
|
||||||
assert.deepEqual(newScenario.Generators, {
|
assert.equal(Object.keys(newScenario.Generators).length, 2);
|
||||||
pu2: {
|
assert("gen1" in newScenario.Generators);
|
||||||
Bus: "b1",
|
assert("pu2" in newScenario.Generators);
|
||||||
Type: "Profiled",
|
|
||||||
"Cost ($/MW)": 120,
|
|
||||||
"Maximum power (MW)": [50, 50, 50, 50, 50],
|
|
||||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test("renameGenerator", () => {
|
test("renameGenerator", () => {
|
||||||
const [newScenario, err] = renameGenerator("pu1", "pu5", TEST_DATA_1);
|
const [newScenario, err] = renameGenerator("pu1", "pu5", TEST_DATA_1);
|
||||||
assert(err === null);
|
assert(err === null);
|
||||||
assert.deepEqual(newScenario.Generators, {
|
assert.deepEqual(newScenario.Generators["pu5"], {
|
||||||
pu5: {
|
Bus: "b1",
|
||||||
Bus: "b1",
|
Type: "Profiled",
|
||||||
Type: "Profiled",
|
"Cost ($/MW)": 12.5,
|
||||||
"Cost ($/MW)": 12.5,
|
"Maximum power (MW)": [10, 12, 13, 15, 20],
|
||||||
"Maximum power (MW)": [10, 12, 13, 15, 20],
|
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
||||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
});
|
||||||
},
|
assert.deepEqual(newScenario.Generators["pu2"], {
|
||||||
pu2: {
|
Bus: "b1",
|
||||||
Bus: "b1",
|
Type: "Profiled",
|
||||||
Type: "Profiled",
|
"Cost ($/MW)": 120,
|
||||||
"Cost ($/MW)": 120,
|
"Maximum power (MW)": [50, 50, 50, 50, 50],
|
||||||
"Maximum power (MW)": [50, 50, 50, 50, 50],
|
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
||||||
"Minimum power (MW)": [0, 0, 0, 0, 0],
|
|
||||||
},
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -13,6 +13,23 @@ export const TEST_DATA_1: UnitCommitmentScenario = {
|
|||||||
b3: { "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268] },
|
b3: { "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268] },
|
||||||
},
|
},
|
||||||
Generators: {
|
Generators: {
|
||||||
|
gen1: {
|
||||||
|
Bus: "b1",
|
||||||
|
Type: "Thermal",
|
||||||
|
"Production cost curve (MW)": [100.0, 110.0, 130.0, 135.0],
|
||||||
|
"Production cost curve ($)": [1400.0, 1600.0, 2200.0, 2400.0],
|
||||||
|
"Startup costs ($)": [300.0, 400.0],
|
||||||
|
"Startup delays (h)": [1, 4],
|
||||||
|
"Ramp up limit (MW)": 232.68,
|
||||||
|
"Ramp down limit (MW)": 232.68,
|
||||||
|
"Startup limit (MW)": 232.68,
|
||||||
|
"Shutdown limit (MW)": 232.68,
|
||||||
|
"Minimum downtime (h)": 4,
|
||||||
|
"Minimum uptime (h)": 4,
|
||||||
|
"Initial status (h)": 12,
|
||||||
|
"Initial power (MW)": 115,
|
||||||
|
"Must run?": false,
|
||||||
|
},
|
||||||
pu1: {
|
pu1: {
|
||||||
Bus: "b1",
|
Bus: "b1",
|
||||||
Type: "Profiled",
|
Type: "Profiled",
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ export interface Buses {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface Generators {
|
export interface Generators {
|
||||||
[name: string]: ProfiledUnit;
|
[name: string]: ProfiledUnit | ThermalUnit;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ProfiledUnit {
|
export interface ProfiledUnit {
|
||||||
@@ -20,6 +20,24 @@ export interface ProfiledUnit {
|
|||||||
"Cost ($/MW)": number;
|
"Cost ($/MW)": number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface ThermalUnit {
|
||||||
|
Bus: string;
|
||||||
|
Type: "Thermal";
|
||||||
|
"Production cost curve (MW)": number[];
|
||||||
|
"Production cost curve ($)": number[];
|
||||||
|
"Startup costs ($)": number[];
|
||||||
|
"Startup delays (h)": number[];
|
||||||
|
"Ramp up limit (MW)": number;
|
||||||
|
"Ramp down limit (MW)": number;
|
||||||
|
"Startup limit (MW)": number;
|
||||||
|
"Shutdown limit (MW)": number;
|
||||||
|
"Minimum downtime (h)": number;
|
||||||
|
"Minimum uptime (h)": number;
|
||||||
|
"Initial status (h)": number;
|
||||||
|
"Initial power (MW)": number;
|
||||||
|
"Must run?": boolean;
|
||||||
|
}
|
||||||
|
|
||||||
export interface UnitCommitmentScenario {
|
export interface UnitCommitmentScenario {
|
||||||
Parameters: {
|
Parameters: {
|
||||||
Version: string;
|
Version: string;
|
||||||
@@ -31,6 +49,29 @@ export interface UnitCommitmentScenario {
|
|||||||
Generators: Generators;
|
Generators: Generators;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getTypedGenerators = <T extends any>(
|
||||||
|
scenario: UnitCommitmentScenario,
|
||||||
|
type: string,
|
||||||
|
): {
|
||||||
|
[key: string]: T;
|
||||||
|
} => {
|
||||||
|
const selected: { [key: string]: T } = {};
|
||||||
|
for (const [name, gen] of Object.entries(scenario.Generators)) {
|
||||||
|
if (gen["Type"] === type) selected[name] = gen as T;
|
||||||
|
}
|
||||||
|
return selected;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getProfiledGenerators = (
|
||||||
|
scenario: UnitCommitmentScenario,
|
||||||
|
): { [key: string]: ProfiledUnit } =>
|
||||||
|
getTypedGenerators<ProfiledUnit>(scenario, "Profiled");
|
||||||
|
|
||||||
|
export const getThermalGenerators = (
|
||||||
|
scenario: UnitCommitmentScenario,
|
||||||
|
): { [key: string]: ThermalUnit } =>
|
||||||
|
getTypedGenerators<ThermalUnit>(scenario, "Thermal");
|
||||||
|
|
||||||
export const BLANK_SCENARIO: UnitCommitmentScenario = {
|
export const BLANK_SCENARIO: UnitCommitmentScenario = {
|
||||||
Parameters: {
|
Parameters: {
|
||||||
Version: "0.4",
|
Version: "0.4",
|
||||||
@@ -117,5 +158,22 @@ export const TEST_SCENARIO: UnitCommitmentScenario = {
|
|||||||
],
|
],
|
||||||
"Cost ($/MW)": 50.0,
|
"Cost ($/MW)": 50.0,
|
||||||
},
|
},
|
||||||
|
gen1: {
|
||||||
|
Bus: "b1",
|
||||||
|
Type: "Thermal",
|
||||||
|
"Production cost curve (MW)": [100.0, 110.0, 130.0, 135.0],
|
||||||
|
"Production cost curve ($)": [1400.0, 1600.0, 2200.0, 2400.0],
|
||||||
|
"Startup costs ($)": [300.0, 400.0],
|
||||||
|
"Startup delays (h)": [1, 4],
|
||||||
|
"Ramp up limit (MW)": 232.68,
|
||||||
|
"Ramp down limit (MW)": 232.68,
|
||||||
|
"Startup limit (MW)": 232.68,
|
||||||
|
"Shutdown limit (MW)": 232.68,
|
||||||
|
"Minimum downtime (h)": 4,
|
||||||
|
"Minimum uptime (h)": 4,
|
||||||
|
"Initial status (h)": 12,
|
||||||
|
"Initial power (MW)": 115,
|
||||||
|
"Must run?": false,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user