web: Update nullable number handling

web
Alinson S. Xavier 3 months ago
parent fff70cce67
commit 53489c1638

@ -13,7 +13,6 @@ import "tabulator-tables/dist/css/tabulator.min.css";
import "../Common/Forms/Tables.css"; import "../Common/Forms/Tables.css";
import { useState } from "react"; import { useState } from "react";
import Footer from "./Footer"; import Footer from "./Footer";
import { validate } from "../../core/Data/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";
@ -68,19 +67,14 @@ const CaseBuilder = () => {
setAndSaveScenario(newScenario); setAndSaveScenario(newScenario);
}; };
const onLoad = (scenario: UnitCommitmentScenario) => { const onLoad = (data: any) => {
const preprocessed = preprocess( const json = JSON.parse(data);
scenario, const [scenario, err] = preprocess(json);
) as unknown as UnitCommitmentScenario; if (err) {
setToastMessage(err.message);
// Validate and assign default values
if (!validate(preprocessed)) {
setToastMessage("Error loading JSON file");
console.error(validate.errors);
return; return;
} }
setAndSaveScenario(scenario!);
setAndSaveScenario(preprocessed);
setToastMessage("Data loaded successfully"); setToastMessage("Data loaded successfully");
}; };

@ -22,8 +22,7 @@ function Header(props: HeaderProps) {
function onLoad() { function onLoad() {
fileElem.current!.showFilePicker((data: any) => { fileElem.current!.showFilePicker((data: any) => {
const scenario = JSON.parse(data) as UnitCommitmentScenario; props.onLoad(data);
props.onLoad(scenario);
}); });
} }

@ -51,13 +51,13 @@ export const ThermalUnitsColumnSpec: ColumnSpec[] = [
title: "Production cost curve (MW)", title: "Production cost curve (MW)",
type: "number[N]", type: "number[N]",
length: 10, length: 10,
width: 75, width: 80,
}, },
{ {
title: "Production cost curve ($)", title: "Production cost curve ($)",
type: "number[N]", type: "number[N]",
length: 10, length: 10,
width: 75, width: 80,
}, },
{ {
title: "Startup costs ($)", title: "Startup costs ($)",
@ -83,22 +83,22 @@ export const ThermalUnitsColumnSpec: ColumnSpec[] = [
}, },
{ {
title: "Ramp up limit (MW)", title: "Ramp up limit (MW)",
type: "number", type: "number?",
width: 100, width: 100,
}, },
{ {
title: "Ramp down limit (MW)", title: "Ramp down limit (MW)",
type: "number", type: "number?",
width: 100, width: 100,
}, },
{ {
title: "Startup limit (MW)", title: "Startup limit (MW)",
type: "number", type: "number?",
width: 80, width: 80,
}, },
{ {
title: "Shutdown limit (MW)", title: "Shutdown limit (MW)",
type: "number", type: "number?",
width: 100, width: 100,
}, },
{ {

@ -55,12 +55,12 @@ export const TransmissionLinesColumnSpec: ColumnSpec[] = [
}, },
{ {
title: "Normal flow limit (MW)", title: "Normal flow limit (MW)",
type: "number", type: "number?",
width: 60, width: 60,
}, },
{ {
title: "Emergency flow limit (MW)", title: "Emergency flow limit (MW)",
type: "number", type: "number?",
width: 60, width: 60,
}, },
{ {

@ -17,7 +17,14 @@ import { UnitCommitmentScenario } from "../../../core/Data/types";
export interface ColumnSpec { export interface ColumnSpec {
title: string; title: string;
type: "string" | "number" | "number[N]" | "number[T]" | "busRef" | "boolean"; type:
| "string"
| "number"
| "number?"
| "number[N]"
| "number[T]"
| "busRef"
| "boolean";
length?: number; length?: number;
width: number; width: number;
} }
@ -53,6 +60,7 @@ export const generateTableColumns = (
}); });
break; break;
case "number": case "number":
case "number?":
columns.push({ columns.push({
...columnsCommonAttrs, ...columnsCommonAttrs,
title: spec.title, title: spec.title,
@ -118,6 +126,7 @@ export const generateTableData = (
switch (spec.type) { switch (spec.type) {
case "string": case "string":
case "number": case "number":
case "number?":
case "boolean": case "boolean":
case "busRef": case "busRef":
entry[spec.title] = entryData[spec.title]; entry[spec.title] = entryData[spec.title];
@ -285,7 +294,7 @@ export const parseCsv = (
export const floatFormatter = (cell: CellComponent) => { export const floatFormatter = (cell: CellComponent) => {
const v = cell.getValue(); const v = cell.getValue();
if (v === "") { if (v === "" || v === null) {
return "—"; return "—";
} else { } else {
return parseFloat(cell.getValue()).toLocaleString("en-US", { return parseFloat(cell.getValue()).toLocaleString("en-US", {

@ -120,8 +120,8 @@ export const TEST_SCENARIO: UnitCommitmentScenario = {
"Source bus": "b1", "Source bus": "b1",
"Target bus": "b2", "Target bus": "b2",
"Susceptance (S)": 29.49686, "Susceptance (S)": 29.49686,
"Normal flow limit (MW)": 15000.0, "Normal flow limit (MW)": null,
"Emergency flow limit (MW)": 20000.0, "Emergency flow limit (MW)": null,
"Flow limit penalty ($/MW)": 5000.0, "Flow limit penalty ($/MW)": 5000.0,
}, },
}, },

@ -97,15 +97,18 @@ export const schema = {
}, },
"Susceptance (S)": { "Susceptance (S)": {
type: "number", type: "number",
minimum: 0,
}, },
"Normal flow limit (MW)": { "Normal flow limit (MW)": {
type: "number", type: "number",
minimum: 0, minimum: 0,
nullable: true,
default: null,
}, },
"Emergency flow limit (MW)": { "Emergency flow limit (MW)": {
type: "number", type: "number",
minimum: 0, minimum: 0,
nullable: true,
default: null,
}, },
"Flow limit penalty ($/MW)": { "Flow limit penalty ($/MW)": {
type: "number", type: "number",
@ -254,18 +257,26 @@ export const schema = {
"Ramp up limit (MW)": { "Ramp up limit (MW)": {
type: "number", type: "number",
minimum: 0, minimum: 0,
nullable: true,
default: null,
}, },
"Ramp down limit (MW)": { "Ramp down limit (MW)": {
type: "number", type: "number",
minimum: 0, minimum: 0,
nullable: true,
default: null,
}, },
"Startup limit (MW)": { "Startup limit (MW)": {
type: "number", type: "number",
minimum: 0, minimum: 0,
nullable: true,
default: null,
}, },
"Shutdown limit (MW)": { "Shutdown limit (MW)": {
type: "number", type: "number",
minimum: 0, minimum: 0,
nullable: true,
default: null,
}, },
"Initial status (h)": { "Initial status (h)": {
type: "integer", type: "integer",

@ -25,10 +25,10 @@ export interface ThermalUnit {
"Production cost curve ($)": number[]; "Production cost curve ($)": number[];
"Startup costs ($)": number[]; "Startup costs ($)": number[];
"Startup delays (h)": number[]; "Startup delays (h)": number[];
"Ramp up limit (MW)": number | ""; "Ramp up limit (MW)": number | null;
"Ramp down limit (MW)": number | ""; "Ramp down limit (MW)": number | null;
"Startup limit (MW)": number | ""; "Startup limit (MW)": number | null;
"Shutdown limit (MW)": number | ""; "Shutdown limit (MW)": number | null;
"Minimum downtime (h)": number; "Minimum downtime (h)": number;
"Minimum uptime (h)": number; "Minimum uptime (h)": number;
"Initial status (h)": number; "Initial status (h)": number;
@ -40,8 +40,8 @@ export interface TransmissionLine {
"Source bus": string; "Source bus": string;
"Target bus": string; "Target bus": string;
"Susceptance (S)": number; "Susceptance (S)": number;
"Normal flow limit (MW)": number; "Normal flow limit (MW)": number | null;
"Emergency flow limit (MW)": number; "Emergency flow limit (MW)": number | null;
"Flow limit penalty ($/MW)": number; "Flow limit penalty ($/MW)": number;
} }

@ -43,6 +43,9 @@ export const generateUniqueName = (container: any, prefix: string): string => {
export const parseNumber = ( export const parseNumber = (
valueStr: string, valueStr: string,
): [number, ValidationError | null] => { ): [number, ValidationError | null] => {
if (valueStr === "") {
return [0, { message: "Field must not be blank" }];
}
const valueFloat = parseFloat(valueStr); const valueFloat = parseFloat(valueStr);
if (isNaN(valueFloat)) { if (isNaN(valueFloat)) {
return [0, { message: `"${valueStr}" is not a valid number` }]; return [0, { message: `"${valueStr}" is not a valid number` }];
@ -51,6 +54,13 @@ export const parseNumber = (
} }
}; };
export const parseNullableNumber = (
valueStr: string,
): [number | null, ValidationError | null] => {
if (valueStr === "") return [null, null];
return parseNumber(valueStr);
};
export const parseBool = ( export const parseBool = (
valueStr: string, valueStr: string,
): [boolean, ValidationError | null] => { ): [boolean, ValidationError | null] => {
@ -93,9 +103,12 @@ export const changeNumberData = (
field: string, field: string,
newValueStr: string, newValueStr: string,
container: { [key: string]: any }, container: { [key: string]: any },
nullable: boolean = false,
): [{ [key: string]: any }, ValidationError | null] => { ): [{ [key: string]: any }, ValidationError | null] => {
// Parse value // Parse value
const [newValueFloat, err] = parseNumber(newValueStr); const [newValueFloat, err] = nullable
? parseNullableNumber(newValueStr)
: parseNumber(newValueStr);
if (err) return [container, err]; if (err) return [container, err];
// Build the new object // Build the new object
@ -211,6 +224,8 @@ 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?":
return changeNumberData(fieldName, newValueStr, container, true);
case "number[T]": case "number[T]":
return changeNumberVecTData( return changeNumberVecTData(
fieldName, fieldName,

@ -61,10 +61,10 @@ export const createThermalUnit = (
"Production cost curve ($)": [0, 10], "Production cost curve ($)": [0, 10],
"Startup costs ($)": [0], "Startup costs ($)": [0],
"Startup delays (h)": [1], "Startup delays (h)": [1],
"Ramp up limit (MW)": "", "Ramp up limit (MW)": null,
"Ramp down limit (MW)": "", "Ramp down limit (MW)": null,
"Startup limit (MW)": "", "Startup limit (MW)": null,
"Shutdown limit (MW)": "", "Shutdown limit (MW)": null,
"Minimum downtime (h)": 1, "Minimum downtime (h)": 1,
"Minimum uptime (h)": 1, "Minimum uptime (h)": 1,
"Initial status (h)": -24, "Initial status (h)": -24,

@ -1,25 +1,35 @@
// @ts-nocheck
/* /*
* UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment * UnitCommitment.jl: Optimization Package for Security-Constrained Unit Commitment
* Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved. * Copyright (C) 2020-2025, UChicago Argonne, LLC. All rights reserved.
* 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 { validate } from "../Data/validate"; import { validate, ValidationError } from "../Data/validate";
import { UnitCommitmentScenario } from "../Data/types";
import { migrate } from "../Data/migrate";
export const preprocess = (data) => { export const preprocess = (
data: any,
): [UnitCommitmentScenario | null, ValidationError | null] => {
// Make a copy of the original data // Make a copy of the original data
let result = JSON.parse(JSON.stringify(data)); let result = JSON.parse(JSON.stringify(data));
// Run migration
migrate(result);
// Run JSON validation and assign default values // Run JSON validation and assign default values
if (!validate(result)) { if (!validate(result)) {
console.error(validate.errors); console.error(validate.errors);
throw Error("Invalid JSON"); return [
null,
{ message: "Invalid JSON file. See console for more details." },
];
} }
// Expand scalars into arrays // Expand scalars into arrays
// @ts-ignore
const timeHorizon = result["Parameters"]["Time horizon (h)"]; const timeHorizon = result["Parameters"]["Time horizon (h)"];
// @ts-ignore
const timeStep = result["Parameters"]["Time step (min)"]; const timeStep = result["Parameters"]["Time step (min)"];
const T = (timeHorizon * 60) / timeStep; const T = (timeHorizon * 60) / timeStep;
for (const busName in result["Buses"]) { for (const busName in result["Buses"]) {
@ -30,5 +40,7 @@ export const preprocess = (data) => {
busData["Load (MW)"] = Array(T).fill(busLoad); busData["Load (MW)"] = Array(T).fill(busLoad);
} }
} }
return result;
const scenario = result as unknown as UnitCommitmentScenario;
return [scenario, null];
}; };

Loading…
Cancel
Save