Compare commits

..

No commits in common. '012331c4bd2fcac1f50dd808f2e0da0181259e1b' and '9d48112bb9c7121663f1f332d7317e933d84ba84' have entirely different histories.

@ -4,44 +4,43 @@
* Released under the modified BSD license. See COPYING.md for more details. * Released under the modified BSD license. See COPYING.md for more details.
*/ */
import SectionHeader from "../Common/SectionHeader/SectionHeader"; import SectionHeader from "../../Common/SectionHeader/SectionHeader";
import SectionButton from "../Common/Buttons/SectionButton"; import SectionButton from "../../Common/Buttons/SectionButton";
import { import {
faDownload, faDownload,
faPlus, faPlus,
faUpload, faUpload,
} from "@fortawesome/free-solid-svg-icons"; } from "@fortawesome/free-solid-svg-icons";
import { offerDownload } from "../Common/io"; import { offerDownload } from "../../Common/io";
import FileUploadElement from "../Common/Buttons/FileUploadElement"; import FileUploadElement from "../../Common/Buttons/FileUploadElement";
import { useRef } from "react"; import { useRef } from "react";
import { ValidationError } from "../../core/Validation/validate"; import { ValidationError } from "../../../core/Validation/validate";
import DataTable, { import DataTable, {
ColumnSpec, ColumnSpec,
generateCsv, generateCsv,
generateTableColumns, generateTableColumns,
generateTableData, generateTableData,
parseCsv, parseCsv,
} from "../Common/Forms/DataTable"; } from "../../Common/Forms/DataTable";
import { UnitCommitmentScenario } from "../../core/fixtures"; import { UnitCommitmentScenario } from "../../../core/fixtures";
import { ColumnDefinition } from "tabulator-tables"; import { ColumnDefinition } from "tabulator-tables";
import { import {
changeBusData, changeBusData,
createBus, createBus,
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: 100, width: 150,
}, },
{ {
title: "Load (MW)", title: "Load (MW)",
type: "number[T]", type: "number[]",
width: 60, width: 60,
}, },
]; ];
@ -53,8 +52,13 @@ 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: CaseBuilderSectionProps) { function BusesComponent(props: BusesProps) {
const fileUploadElem = useRef<FileUploadElement>(null); const fileUploadElem = useRef<FileUploadElement>(null);
const onDownload = () => { const onDownload = () => {

@ -5,8 +5,8 @@
*/ */
import Header from "./Header"; import Header from "./Header";
import Parameters from "./Parameters"; import Parameters from "./Parameters/Parameters";
import BusesComponent from "./Buses"; import Buses from "./Buses/Buses";
import { import {
BLANK_SCENARIO, BLANK_SCENARIO,
TEST_SCENARIO, TEST_SCENARIO,
@ -21,14 +21,7 @@ import { validate } from "../../core/Validation/validate";
import { offerDownload } from "../Common/io"; 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/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(() => {
@ -36,22 +29,11 @@ const CaseBuilder = () => {
// return savedScenario ? JSON.parse(savedScenario) : TEST_SCENARIO; // return savedScenario ? JSON.parse(savedScenario) : TEST_SCENARIO;
return TEST_SCENARIO; return TEST_SCENARIO;
}); });
const [undoStack, setUndoStack] = useState<UnitCommitmentScenario[]>([]);
const [toastMessage, setToastMessage] = useState<string>(""); const [toastMessage, setToastMessage] = useState<string>("");
const setAndSaveScenario = ( const setAndSaveScenario = (scenario: UnitCommitmentScenario) => {
newScenario: UnitCommitmentScenario, setScenario(scenario);
updateUndoStack = true, localStorage.setItem("scenario", JSON.stringify(scenario));
) => {
if (updateUndoStack) {
const newUndoStack = [...undoStack, scenario];
if (newUndoStack.length > 25) {
newUndoStack.splice(0, newUndoStack.length - 25);
}
setUndoStack(newUndoStack);
}
setScenario(newScenario);
localStorage.setItem("scenario", JSON.stringify(newScenario));
}; };
const onClear = () => { const onClear = () => {
@ -86,32 +68,16 @@ const CaseBuilder = () => {
setToastMessage("Data loaded successfully"); setToastMessage("Data loaded successfully");
}; };
const onUndo = () => {
if (undoStack.length === 0) return;
setUndoStack(undoStack.slice(0, -1));
setAndSaveScenario(undoStack[undoStack.length - 1]!, false);
};
return ( return (
<div> <div>
<Header <Header onClear={onClear} onSave={onSave} onLoad={onLoad} />
onClear={onClear}
onSave={onSave}
onLoad={onLoad}
onUndo={onUndo}
/>
<div className="content"> <div className="content">
<Parameters <Parameters
scenario={scenario} scenario={scenario}
onDataChanged={onDataChanged} onDataChanged={onDataChanged}
onError={setToastMessage} onError={setToastMessage}
/> />
<BusesComponent <Buses
scenario={scenario}
onDataChanged={onDataChanged}
onError={setToastMessage}
/>
<ThermalUnitsComponent
scenario={scenario} scenario={scenario}
onDataChanged={onDataChanged} onDataChanged={onDataChanged}
onError={setToastMessage} onError={setToastMessage}

@ -13,7 +13,6 @@ import FileUploadElement from "../Common/Buttons/FileUploadElement";
interface HeaderProps { interface HeaderProps {
onClear: () => void; onClear: () => void;
onSave: () => void; onSave: () => void;
onUndo: () => void;
onLoad: (data: UnitCommitmentScenario) => void; onLoad: (data: UnitCommitmentScenario) => void;
} }
@ -33,7 +32,6 @@ function Header(props: HeaderProps) {
<h1>UnitCommitment.jl</h1> <h1>UnitCommitment.jl</h1>
<h2>Case Builder</h2> <h2>Case Builder</h2>
<div className={styles.buttonContainer}> <div className={styles.buttonContainer}>
<SiteHeaderButton title="Undo" onClick={props.onUndo} />
<SiteHeaderButton title="Clear" onClick={props.onClear} /> <SiteHeaderButton title="Clear" onClick={props.onClear} />
<SiteHeaderButton title="Load" onClick={onLoad} /> <SiteHeaderButton title="Load" onClick={onLoad} />
<SiteHeaderButton title="Save" onClick={props.onSave} /> <SiteHeaderButton title="Save" onClick={props.onSave} />

@ -4,15 +4,15 @@
* Released under the modified BSD license. See COPYING.md for more details. * Released under the modified BSD license. See COPYING.md for more details.
*/ */
import SectionHeader from "../Common/SectionHeader/SectionHeader"; import SectionHeader from "../../Common/SectionHeader/SectionHeader";
import Form from "../Common/Forms/Form"; import Form from "../../Common/Forms/Form";
import TextInputRow from "../Common/Forms/TextInputRow"; import TextInputRow from "../../Common/Forms/TextInputRow";
import { UnitCommitmentScenario } from "../../core/fixtures"; import { UnitCommitmentScenario } from "../../../core/fixtures";
import { import {
changeParameter, changeParameter,
changeTimeHorizon, changeTimeHorizon,
changeTimeStep, changeTimeStep,
} from "../../core/Operations/parameterOps"; } from "../../../core/Operations/parameterOps";
interface ParametersProps { interface ParametersProps {
scenario: UnitCommitmentScenario; scenario: UnitCommitmentScenario;

@ -4,8 +4,8 @@
* Released under the modified BSD license. See COPYING.md for more details. * Released under the modified BSD license. See COPYING.md for more details.
*/ */
import SectionHeader from "../Common/SectionHeader/SectionHeader"; import SectionHeader from "../../Common/SectionHeader/SectionHeader";
import SectionButton from "../Common/Buttons/SectionButton"; import SectionButton from "../../Common/Buttons/SectionButton";
import { import {
faDownload, faDownload,
faPlus, faPlus,
@ -17,34 +17,36 @@ import DataTable, {
generateTableColumns, generateTableColumns,
generateTableData, generateTableData,
parseCsv, parseCsv,
} from "../Common/Forms/DataTable"; } from "../../Common/Forms/DataTable";
import { import { UnitCommitmentScenario } from "../../../core/fixtures";
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";
import { useRef } from "react"; import { useRef } from "react";
import { import {
changeProfiledUnitData, changeProfiledUnitData,
createProfiledUnit, createProfiledUnit,
deleteGenerator, deleteGenerator,
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: 100, width: 150,
}, },
{ {
title: "Bus", title: "Bus",
type: "busRef", type: "busRef",
width: 100, width: 150,
}, },
{ {
title: "Cost ($/MW)", title: "Cost ($/MW)",
@ -53,12 +55,12 @@ export const ProfiledUnitsColumnSpec: ColumnSpec[] = [
}, },
{ {
title: "Maximum power (MW)", title: "Maximum power (MW)",
type: "number[T]", type: "number[]",
width: 60, width: 60,
}, },
{ {
title: "Minimum power (MW)", title: "Minimum power (MW)",
type: "number[T]", type: "number[]",
width: 60, width: 60,
}, },
]; ];
@ -68,14 +70,14 @@ const generateProfiledUnitsData = (
): [any[], ColumnDefinition[]] => { ): [any[], ColumnDefinition[]] => {
const columns = generateTableColumns(scenario, ProfiledUnitsColumnSpec); const columns = generateTableColumns(scenario, ProfiledUnitsColumnSpec);
const data = generateTableData( const data = generateTableData(
getProfiledGenerators(scenario), scenario.Generators,
ProfiledUnitsColumnSpec, ProfiledUnitsColumnSpec,
scenario, scenario,
); );
return [data, columns]; return [data, columns];
}; };
const ProfiledUnitsComponent = (props: CaseBuilderSectionProps) => { const ProfiledUnitsComponent = (props: ProfiledUnitsProps) => {
const fileUploadElem = useRef<FileUploadElement>(null); const fileUploadElem = useRef<FileUploadElement>(null);
const onDownload = () => { const onDownload = () => {

@ -1,228 +0,0 @@
/*
* 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: "Minimum uptime (h)",
type: "number",
width: 80,
},
{
title: "Minimum 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;

@ -5,124 +5,13 @@
*/ */
import assert from "node:assert"; import assert from "node:assert";
import { BusesColumnSpec, generateBusesData } from "../../CaseBuilder/Buses";
import { import {
floatFormatter, BusesColumnSpec,
generateCsv, generateBusesData,
generateTableColumns, } from "../../CaseBuilder/Buses/Buses";
generateTableData, import { generateCsv, parseCsv } from "./DataTable";
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/ProfiledUnits";
import { ThermalUnitsColumnSpec } from "../../CaseBuilder/ThermalUnits";
import { getThermalGenerators } from "../../../core/fixtures";
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("generateTableData (ThermalUnits)", () => {
const data = generateTableData(
getThermalGenerators(TEST_DATA_1),
ThermalUnitsColumnSpec,
TEST_DATA_1,
);
assert.deepEqual(data[0], {
Name: "gen1",
Bus: "b1",
"Initial power (MW)": 115,
"Initial status (h)": 12,
"Minimum downtime (h)": 4,
"Minimum uptime (h)": 4,
"Ramp down limit (MW)": 232.68,
"Ramp up limit (MW)": 232.68,
"Shutdown limit (MW)": 232.68,
"Startup limit (MW)": 232.68,
"Production cost curve ($) 1": 1400,
"Production cost curve ($) 2": 1600,
"Production cost curve ($) 3": 2200,
"Production cost curve ($) 4": 2400,
"Production cost curve ($) 5": "",
"Production cost curve ($) 6": "",
"Production cost curve ($) 7": "",
"Production cost curve ($) 8": "",
"Production cost curve ($) 9": "",
"Production cost curve ($) 10": "",
"Production cost curve (MW) 1": 100,
"Production cost curve (MW) 2": 110,
"Production cost curve (MW) 3": 130,
"Production cost curve (MW) 4": 135,
"Production cost curve (MW) 5": "",
"Production cost curve (MW) 6": "",
"Production cost curve (MW) 7": "",
"Production cost curve (MW) 8": "",
"Production cost curve (MW) 9": "",
"Production cost curve (MW) 10": "",
"Startup costs ($) 1": 300,
"Startup costs ($) 2": 400,
"Startup costs ($) 3": "",
"Startup costs ($) 4": "",
"Startup costs ($) 5": "",
"Startup delays (h) 1": 1,
"Startup delays (h) 2": 4,
"Startup delays (h) 3": "",
"Startup delays (h) 4": "",
"Startup delays (h) 5": "",
});
});
test("generate CSV", () => { test("generate CSV", () => {
const [data, columns] = generateBusesData(TEST_DATA_1); const [data, columns] = generateBusesData(TEST_DATA_1);

@ -17,8 +17,7 @@ import { parseNumber } from "../../../core/Operations/commonOps";
export interface ColumnSpec { export interface ColumnSpec {
title: string; title: string;
type: "string" | "number" | "number[N]" | "number[T]" | "busRef" | "boolean"; type: "string" | "number" | "number[]" | "busRef";
length?: number;
width: number; width: number;
} }
@ -29,7 +28,6 @@ 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 "busRef": case "busRef":
@ -40,18 +38,6 @@ export const generateTableColumns = (
minWidth: spec.width, minWidth: spec.width,
}); });
break; break;
case "boolean":
columns.push({
...columnsCommonAttrs,
title: spec.title,
field: spec.title,
minWidth: spec.width,
editor: "list",
editorParams: {
values: [true, false],
},
});
break;
case "number": case "number":
columns.push({ columns.push({
...columnsCommonAttrs, ...columnsCommonAttrs,
@ -61,7 +47,8 @@ export const generateTableColumns = (
formatter: floatFormatter, formatter: floatFormatter,
}); });
break; break;
case "number[T]": case "number[]":
const subColumns: ColumnDefinition[] = [];
timeSlots.forEach((t) => { timeSlots.forEach((t) => {
subColumns.push({ subColumns.push({
...columnsCommonAttrs, ...columnsCommonAttrs,
@ -76,21 +63,6 @@ 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}`);
} }
@ -118,22 +90,16 @@ export const generateTableData = (
switch (spec.type) { switch (spec.type) {
case "string": case "string":
case "number": case "number":
case "boolean":
case "busRef": case "busRef":
entry[spec.title] = entryData[spec.title]; entry[spec.title] = entryData[spec.title];
break; break;
case "number[T]": case "number[]":
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];
} }
break; break;
case "number[N]":
for (let i = 0; i < spec.length!; i++) {
entry[`${spec.title} ${i + 1}`] = entryData[spec.title][i] || "";
}
break;
default: default:
throw Error(`Unknown type: ${spec.type}`); console.error(`Unknown type: ${spec.type}`);
} }
} }
data.push(entry); data.push(entry);
@ -241,7 +207,7 @@ export const parseCsv = (
} }
data[name][spec.title] = row[spec.title]; data[name][spec.title] = row[spec.title];
break; break;
case "number[T]": case "number[]":
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++) {
@ -260,12 +226,7 @@ export const parseCsv = (
}; };
export const floatFormatter = (cell: CellComponent) => { export const floatFormatter = (cell: CellComponent) => {
const v = cell.getValue(); return parseFloat(cell.getValue()).toFixed(1);
if (v === "") {
return "&mdash;";
} else {
return parseFloat(cell.getValue()).toFixed(1);
}
}; };
export const generateTimeslots = (scenario: UnitCommitmentScenario) => { export const generateTimeslots = (scenario: UnitCommitmentScenario) => {

@ -12,7 +12,7 @@ import {
generateUniqueName, generateUniqueName,
renameItemInObject, renameItemInObject,
} from "./commonOps"; } from "./commonOps";
import { BusesColumnSpec } from "../../components/CaseBuilder/Buses"; import { BusesColumnSpec } from "../../components/CaseBuilder/Buses/Buses";
export const createBus = (scenario: UnitCommitmentScenario) => { export const createBus = (scenario: UnitCommitmentScenario) => {
const name = generateUniqueName(scenario.Buses, "b"); const name = generateUniqueName(scenario.Buses, "b");

@ -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[T]": case "number[]":
return changeNumberVecData( return changeNumberVecData(
fieldName, fieldName,
fieldTime!, fieldTime!,

@ -16,13 +16,28 @@ 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.equal(Object.keys(newScenario.Generators).length, 4); assert.deepEqual(newScenario.Generators, {
assert.deepEqual(newScenario.Generators["pu3"], { pu1: {
Bus: "b1", Bus: "b1",
Type: "Profiled", Type: "Profiled",
"Cost ($/MW)": 0, "Cost ($/MW)": 12.5,
"Maximum power (MW)": [0, 0, 0, 0, 0], "Maximum power (MW)": [10, 12, 13, 15, 20],
"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],
},
}); });
}); });
@ -51,12 +66,21 @@ 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["pu2"], { assert.deepEqual(scenario.Generators, {
Bus: "b3", pu1: {
Type: "Profiled", Bus: "b1",
"Cost ($/MW)": 120, Type: "Profiled",
"Maximum power (MW)": [50, 50, 50, 50, 50], "Cost ($/MW)": 99,
"Minimum power (MW)": [0, 0, 0, 0, 0], "Maximum power (MW)": [10, 12, 13, 99, 20],
"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],
},
}); });
}); });
@ -70,26 +94,34 @@ test("changeProfiledUnitData with invalid bus", () => {
test("deleteGenerator", () => { test("deleteGenerator", () => {
const newScenario = deleteGenerator("pu1", TEST_DATA_1); const newScenario = deleteGenerator("pu1", TEST_DATA_1);
assert.equal(Object.keys(newScenario.Generators).length, 2); assert.deepEqual(newScenario.Generators, {
assert("gen1" in newScenario.Generators); pu2: {
assert("pu2" in newScenario.Generators); Bus: "b1",
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["pu5"], { assert.deepEqual(newScenario.Generators, {
Bus: "b1", pu5: {
Type: "Profiled", Bus: "b1",
"Cost ($/MW)": 12.5, Type: "Profiled",
"Maximum power (MW)": [10, 12, 13, 15, 20], "Cost ($/MW)": 12.5,
"Minimum power (MW)": [0, 0, 0, 0, 0], "Maximum power (MW)": [10, 12, 13, 15, 20],
}); "Minimum power (MW)": [0, 0, 0, 0, 0],
assert.deepEqual(newScenario.Generators["pu2"], { },
Bus: "b1", pu2: {
Type: "Profiled", Bus: "b1",
"Cost ($/MW)": 120, Type: "Profiled",
"Maximum power (MW)": [50, 50, 50, 50, 50], "Cost ($/MW)": 120,
"Minimum power (MW)": [0, 0, 0, 0, 0], "Maximum power (MW)": [50, 50, 50, 50, 50],
"Minimum power (MW)": [0, 0, 0, 0, 0],
},
}); });
}); });

@ -12,7 +12,7 @@ import {
generateUniqueName, generateUniqueName,
renameItemInObject, renameItemInObject,
} from "./commonOps"; } from "./commonOps";
import { ProfiledUnitsColumnSpec } from "../../components/CaseBuilder/ProfiledUnits"; import { ProfiledUnitsColumnSpec } from "../../components/CaseBuilder/ProfiledUnits/ProfiledUnits";
export const createProfiledUnit = ( export const createProfiledUnit = (
scenario: UnitCommitmentScenario, scenario: UnitCommitmentScenario,

@ -13,23 +13,6 @@ 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 | ThermalUnit; [name: string]: ProfiledUnit;
} }
export interface ProfiledUnit { export interface ProfiledUnit {
@ -20,24 +20,6 @@ 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;
@ -49,29 +31,6 @@ 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",
@ -158,22 +117,5 @@ 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,
},
}, },
}; };

Loading…
Cancel
Save