diff --git a/web/src/components/CaseBuilder/ProfiledUnits/ProfiledUnits.tsx b/web/src/components/CaseBuilder/ProfiledUnits/ProfiledUnits.tsx
index a016f85..3351305 100644
--- a/web/src/components/CaseBuilder/ProfiledUnits/ProfiledUnits.tsx
+++ b/web/src/components/CaseBuilder/ProfiledUnits/ProfiledUnits.tsx
@@ -23,6 +23,7 @@ import { ColumnDefinition } from "tabulator-tables";
import { offerDownload } from "../../Common/io";
import FileUploadElement from "../../Common/Buttons/FileUploadElement";
import { useRef } from "react";
+import { createProfiledUnit } from "../../../core/Operations/profiledUnitOps";
interface ProfiledUnitsProps {
scenario: UnitCommitmentScenario;
@@ -98,7 +99,14 @@ const ProfiledUnitsComponent = (props: ProfiledUnitsProps) => {
});
};
- const onAdd = () => {};
+ const onAdd = () => {
+ const [newScenario, err] = createProfiledUnit(props.scenario);
+ if (err) {
+ props.onError(err.message);
+ return;
+ }
+ props.onDataChanged(newScenario);
+ };
return (
diff --git a/web/src/core/Operations/busOperations.test.ts b/web/src/core/Operations/busOperations.test.ts
index 20263e6..14c18c5 100644
--- a/web/src/core/Operations/busOperations.test.ts
+++ b/web/src/core/Operations/busOperations.test.ts
@@ -30,18 +30,10 @@ test("changeBusData", () => {
[scenario, err] = changeBusData("b3", "Load (MW) 04:00", "99", scenario);
assert.equal(err, null);
- assert.deepEqual(scenario, {
- Parameters: {
- Version: "0.4",
- "Power balance penalty ($/MW)": 1000.0,
- "Time horizon (h)": 5,
- "Time step (min)": 60,
- },
- Buses: {
- b1: { "Load (MW)": [99, 34.38835, 33.45083, 99, 33.25044] },
- b2: { "Load (MW)": [14.03739, 13.48563, 13.11797, 12.9009, 13.03939] },
- b3: { "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 99] },
- },
+ assert.deepEqual(scenario.Buses, {
+ b1: { "Load (MW)": [99, 34.38835, 33.45083, 99, 33.25044] },
+ b2: { "Load (MW)": [14.03739, 13.48563, 13.11797, 12.9009, 13.03939] },
+ b3: { "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 99] },
});
});
@@ -54,35 +46,19 @@ test("changeBusData with invalid numbers", () => {
test("deleteBus", () => {
let scenario = TEST_DATA_1;
scenario = deleteBus("b2", scenario);
- assert.deepEqual(scenario, {
- Parameters: {
- Version: "0.4",
- "Power balance penalty ($/MW)": 1000.0,
- "Time horizon (h)": 5,
- "Time step (min)": 60,
- },
- Buses: {
- b1: { "Load (MW)": [35.79534, 34.38835, 33.45083, 32.89729, 33.25044] },
- b3: { "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268] },
- },
+ assert.deepEqual(scenario.Buses, {
+ b1: { "Load (MW)": [35.79534, 34.38835, 33.45083, 32.89729, 33.25044] },
+ b3: { "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268] },
});
});
test("renameBus", () => {
let [scenario, err] = renameBus("b2", "b99", TEST_DATA_1);
assert(err === null);
- assert.deepEqual(scenario, {
- Parameters: {
- Version: "0.4",
- "Power balance penalty ($/MW)": 1000.0,
- "Time horizon (h)": 5,
- "Time step (min)": 60,
- },
- Buses: {
- b1: { "Load (MW)": [35.79534, 34.38835, 33.45083, 32.89729, 33.25044] },
- b99: { "Load (MW)": [14.03739, 13.48563, 13.11797, 12.9009, 13.03939] },
- b3: { "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268] },
- },
+ assert.deepEqual(scenario.Buses, {
+ b1: { "Load (MW)": [35.79534, 34.38835, 33.45083, 32.89729, 33.25044] },
+ b99: { "Load (MW)": [14.03739, 13.48563, 13.11797, 12.9009, 13.03939] },
+ b3: { "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268] },
});
});
diff --git a/web/src/core/Operations/busOperations.ts b/web/src/core/Operations/busOperations.ts
index 74b0bf8..cc80c9e 100644
--- a/web/src/core/Operations/busOperations.ts
+++ b/web/src/core/Operations/busOperations.ts
@@ -6,34 +6,27 @@
import { Buses, UnitCommitmentScenario } from "../fixtures";
import { ValidationError } from "../Validation/validate";
+import { generateTimeslots } from "../../components/Common/Forms/DataTable";
-const generateUniqueBusName = (scenario: UnitCommitmentScenario) => {
- let newBusName = "b";
+export const generateUniqueName = (container: any, prefix: string): string => {
let counter = 1;
- let name = `${newBusName}${counter}`;
- while (name in scenario.Buses) {
+ let name = `${prefix}${counter}`;
+ while (name in container) {
counter++;
- name = `${newBusName}${counter}`;
+ name = `${prefix}${counter}`;
}
return name;
};
-const generateDefaultBusLoad = (scenario: UnitCommitmentScenario) => {
- const T =
- scenario.Parameters["Time horizon (h)"] *
- (60 / scenario.Parameters["Time step (min)"]);
- return new Array(T).fill(0);
-};
-
export const createBus = (scenario: UnitCommitmentScenario) => {
- const load = generateDefaultBusLoad(scenario);
- let name = generateUniqueBusName(scenario);
+ const name = generateUniqueName(scenario.Buses, "b");
+ const timeslots = generateTimeslots(scenario);
return {
...scenario,
Buses: {
...scenario.Buses,
[name]: {
- "Load (MW)": load,
+ "Load (MW)": Array(timeslots.length).fill(0),
},
},
};
diff --git a/web/src/core/Operations/parameterOperations.test.ts b/web/src/core/Operations/parameterOperations.test.ts
index c7ae767..5127369 100644
--- a/web/src/core/Operations/parameterOperations.test.ts
+++ b/web/src/core/Operations/parameterOperations.test.ts
@@ -15,18 +15,16 @@ import { TEST_DATA_1, TEST_DATA_2 } from "../fixtures.test";
test("changeTimeHorizon: Shrink 1", () => {
const [newScenario, err] = changeTimeHorizon(TEST_DATA_1, "3");
assert(err === null);
- assert.deepEqual(newScenario, {
- Parameters: {
- Version: "0.4",
- "Power balance penalty ($/MW)": 1000.0,
- "Time horizon (h)": 3,
- "Time step (min)": 60,
- },
- Buses: {
- b1: { "Load (MW)": [35.79534, 34.38835, 33.45083] },
- b2: { "Load (MW)": [14.03739, 13.48563, 13.11797] },
- b3: { "Load (MW)": [27.3729, 26.29698, 25.58005] },
- },
+ assert.deepEqual(newScenario.Parameters, {
+ Version: "0.4",
+ "Power balance penalty ($/MW)": 1000.0,
+ "Time horizon (h)": 3,
+ "Time step (min)": 60,
+ });
+ assert.deepEqual(newScenario.Buses, {
+ b1: { "Load (MW)": [35.79534, 34.38835, 33.45083] },
+ b2: { "Load (MW)": [14.03739, 13.48563, 13.11797] },
+ b3: { "Load (MW)": [27.3729, 26.29698, 25.58005] },
});
});
@@ -51,23 +49,21 @@ test("changeTimeHorizon: Shrink 2", () => {
test("changeTimeHorizon grow", () => {
const [newScenario, err] = changeTimeHorizon(TEST_DATA_1, "7");
assert(err === null);
- assert.deepEqual(newScenario, {
- Parameters: {
- Version: "0.4",
- "Power balance penalty ($/MW)": 1000.0,
- "Time horizon (h)": 7,
- "Time step (min)": 60,
+ assert.deepEqual(newScenario.Parameters, {
+ Version: "0.4",
+ "Power balance penalty ($/MW)": 1000.0,
+ "Time horizon (h)": 7,
+ "Time step (min)": 60,
+ });
+ assert.deepEqual(newScenario.Buses, {
+ b1: {
+ "Load (MW)": [35.79534, 34.38835, 33.45083, 32.89729, 33.25044, 0, 0],
},
- Buses: {
- b1: {
- "Load (MW)": [35.79534, 34.38835, 33.45083, 32.89729, 33.25044, 0, 0],
- },
- b2: {
- "Load (MW)": [14.03739, 13.48563, 13.11797, 12.9009, 13.03939, 0, 0],
- },
- b3: {
- "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268, 0, 0],
- },
+ b2: {
+ "Load (MW)": [14.03739, 13.48563, 13.11797, 12.9009, 13.03939, 0, 0],
+ },
+ b3: {
+ "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268, 0, 0],
},
});
});
diff --git a/web/src/core/Operations/profiledUnitOps.test.ts b/web/src/core/Operations/profiledUnitOps.test.ts
new file mode 100644
index 0000000..76dd431
--- /dev/null
+++ b/web/src/core/Operations/profiledUnitOps.test.ts
@@ -0,0 +1,36 @@
+/*
+ * 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 { TEST_DATA_1, TEST_DATA_BLANK } from "../fixtures.test";
+import assert from "node:assert";
+import { createProfiledUnit } from "./profiledUnitOps";
+
+test("createUnit", () => {
+ const [newScenario, err] = createProfiledUnit(TEST_DATA_1);
+ assert(err === null);
+ assert.deepEqual(newScenario.Generators, {
+ pu1: {
+ Bus: "b1",
+ Type: "Profiled",
+ "Cost ($/MW)": 12.5,
+ "Maximum power (MW)": [10, 12, 13, 15, 20],
+ "Minimum power (MW)": [0, 0, 0, 0, 0],
+ },
+ pu2: {
+ Bus: "b1",
+ Type: "Profiled",
+ "Cost ($/MW)": 0,
+ "Maximum power (MW)": [0, 0, 0, 0, 0],
+ "Minimum power (MW)": [0, 0, 0, 0, 0],
+ },
+ });
+});
+
+test("createUnit with blank file", () => {
+ const [newScenario, err] = createProfiledUnit(TEST_DATA_BLANK);
+ assert(err !== null);
+ assert.equal(err.message, "Profiled unit requires an existing bus.");
+});
diff --git a/web/src/core/Operations/profiledUnitOps.ts b/web/src/core/Operations/profiledUnitOps.ts
new file mode 100644
index 0000000..6075002
--- /dev/null
+++ b/web/src/core/Operations/profiledUnitOps.ts
@@ -0,0 +1,37 @@
+/*
+ * 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 { UnitCommitmentScenario } from "../fixtures";
+import { generateTimeslots } from "../../components/Common/Forms/DataTable";
+import { generateUniqueName } from "./busOperations";
+import { ValidationError } from "../Validation/validate";
+
+export const createProfiledUnit = (
+ scenario: UnitCommitmentScenario,
+): [UnitCommitmentScenario, ValidationError | null] => {
+ const busNames = Object.keys(scenario.Buses);
+ if (busNames.length === 0) {
+ return [scenario, { message: "Profiled unit requires an existing bus." }];
+ }
+ const timeslots = generateTimeslots(scenario);
+ const name = generateUniqueName(scenario.Generators, "pu");
+ return [
+ {
+ ...scenario,
+ Generators: {
+ ...scenario.Generators,
+ [name]: {
+ Bus: busNames[0]!,
+ Type: "Profiled",
+ "Cost ($/MW)": 0,
+ "Minimum power (MW)": Array(timeslots.length).fill(0),
+ "Maximum power (MW)": Array(timeslots.length).fill(0),
+ },
+ },
+ },
+ null,
+ ];
+};
diff --git a/web/src/core/fixtures.test.ts b/web/src/core/fixtures.test.ts
index e0cc41d..47d069c 100644
--- a/web/src/core/fixtures.test.ts
+++ b/web/src/core/fixtures.test.ts
@@ -12,7 +12,17 @@ export const TEST_DATA_1: UnitCommitmentScenario = {
b2: { "Load (MW)": [14.03739, 13.48563, 13.11797, 12.9009, 13.03939] },
b3: { "Load (MW)": [27.3729, 26.29698, 25.58005, 25.15675, 25.4268] },
},
+ Generators: {
+ pu1: {
+ Bus: "b1",
+ Type: "Profiled",
+ "Cost ($/MW)": 12.5,
+ "Maximum power (MW)": [10, 12, 13, 15, 20],
+ "Minimum power (MW)": [0, 0, 0, 0, 0],
+ },
+ },
};
+
export const TEST_DATA_2: UnitCommitmentScenario = {
Parameters: {
Version: "0.4",
@@ -27,4 +37,15 @@ export const TEST_DATA_2: UnitCommitmentScenario = {
},
};
+export const TEST_DATA_BLANK: UnitCommitmentScenario = {
+ Parameters: {
+ Version: "0.4",
+ "Power balance penalty ($/MW)": 1000.0,
+ "Time horizon (h)": 5,
+ "Time step (min)": 60,
+ },
+ Buses: {},
+ Generators: {},
+};
+
test("fixtures", () => {});