Adding MPS Export #49
@@ -12,9 +12,6 @@ on:
|
|||||||
# Allows you to run this workflow manually from the Actions tab
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
|
|
||||||
# Automatically run on Pull Request
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||||
permissions:
|
permissions:
|
||||||
contents: read
|
contents: read
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
# Sample workflow for building and deploying a Next.js site to GitHub Pages
|
||||||
|
#
|
||||||
|
# To get started with Next.js see: https://nextjs.org/docs/getting-started
|
||||||
|
#
|
||||||
|
name: Build Next.js site
|
||||||
|
|
||||||
|
on:
|
||||||
|
# Allows you to run this workflow manually from the Actions tab
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
# Automatically run on Pull Request
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued.
|
||||||
|
# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete.
|
||||||
|
concurrency:
|
||||||
|
group: "pages"
|
||||||
|
cancel-in-progress: false
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
# Build job
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
- name: Detect package manager
|
||||||
|
id: detect-package-manager
|
||||||
|
run: |
|
||||||
|
if [ -f "${{ github.workspace }}/yarn.lock" ]; then
|
||||||
|
echo "manager=yarn" >> $GITHUB_OUTPUT
|
||||||
|
echo "command=install" >> $GITHUB_OUTPUT
|
||||||
|
echo "runner=yarn" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
elif [ -f "${{ github.workspace }}/package.json" ]; then
|
||||||
|
echo "manager=npm" >> $GITHUB_OUTPUT
|
||||||
|
echo "command=ci" >> $GITHUB_OUTPUT
|
||||||
|
echo "runner=npx --no-install" >> $GITHUB_OUTPUT
|
||||||
|
exit 0
|
||||||
|
else
|
||||||
|
echo "Unable to determine package manager"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
- name: Setup Node
|
||||||
|
uses: actions/setup-node@v4
|
||||||
|
with:
|
||||||
|
node-version: "20"
|
||||||
|
cache: ${{ steps.detect-package-manager.outputs.manager }}
|
||||||
|
- name: Restore cache
|
||||||
|
uses: actions/cache@v4
|
||||||
|
with:
|
||||||
|
path: |
|
||||||
|
.next/cache
|
||||||
|
# Generate a new cache whenever packages or source files change.
|
||||||
|
key: ${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-${{ hashFiles('**.[jt]s', '**.[jt]sx') }}
|
||||||
|
# If source files changed but packages didn't, rebuild from a prior cache.
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-nextjs-${{ hashFiles('**/package-lock.json', '**/yarn.lock') }}-
|
||||||
|
- name: Install dependencies
|
||||||
|
run: ${{ steps.detect-package-manager.outputs.manager }} ${{ steps.detect-package-manager.outputs.command }}
|
||||||
|
- name: Build with Next.js
|
||||||
|
run: ${{ steps.detect-package-manager.outputs.runner }} next build
|
||||||
@@ -24,6 +24,8 @@ export default function text(lang: string, input: string): string {
|
|||||||
return "Listen Sie alle Ihre Variablen auf. Eine pro Zeile (mit der 'Enter'-Taste trennen). Erlaubte Symbole sind a-z, A-Z.\nBeispiel:\nx\ny";
|
return "Listen Sie alle Ihre Variablen auf. Eine pro Zeile (mit der 'Enter'-Taste trennen). Erlaubte Symbole sind a-z, A-Z.\nBeispiel:\nx\ny";
|
||||||
case "boxExportLP":
|
case "boxExportLP":
|
||||||
return "Als LP exportieren";
|
return "Als LP exportieren";
|
||||||
|
case "boxExportMPS":
|
||||||
|
return "Als MPS exportieren";
|
||||||
case "boxOut":
|
case "boxOut":
|
||||||
return "Geben Sie ein Problem ein und drücken Sie eine Aktionstaste, um die Ausgabe anzuzeigen...";
|
return "Geben Sie ein Problem ein und drücken Sie eine Aktionstaste, um die Ausgabe anzuzeigen...";
|
||||||
case "buttonCalc":
|
case "buttonCalc":
|
||||||
@@ -92,8 +94,12 @@ export default function text(lang: string, input: string): string {
|
|||||||
return "Download wird vorbereitet...";
|
return "Download wird vorbereitet...";
|
||||||
case "downloadFetchInput":
|
case "downloadFetchInput":
|
||||||
return "Eingaben werden geladen...";
|
return "Eingaben werden geladen...";
|
||||||
|
case "downloadCheckInput":
|
||||||
|
return "Überprüfe auf leere Eingabefelder...";
|
||||||
case "importing":
|
case "importing":
|
||||||
return "Importiere...";
|
return "Importiere...";
|
||||||
|
case "err_invalidConstraintFormat":
|
||||||
|
return "Fehler: Nicht erlaubter Operator verwendet.";
|
||||||
default:
|
default:
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
@@ -125,6 +131,8 @@ export default function text(lang: string, input: string): string {
|
|||||||
return "List all your variables. One per line (divide by 'return' button). Allowed symbols are a-z, A-Z.\nExample:\nx\ny";
|
return "List all your variables. One per line (divide by 'return' button). Allowed symbols are a-z, A-Z.\nExample:\nx\ny";
|
||||||
case "boxExportLP":
|
case "boxExportLP":
|
||||||
return "Export as LP";
|
return "Export as LP";
|
||||||
|
case "boxExportMPS":
|
||||||
|
return "Export as MPS";
|
||||||
case "boxOut":
|
case "boxOut":
|
||||||
return "Input a problem and an action button to display output...";
|
return "Input a problem and an action button to display output...";
|
||||||
case "buttonCalc":
|
case "buttonCalc":
|
||||||
@@ -193,8 +201,12 @@ export default function text(lang: string, input: string): string {
|
|||||||
return "Preparing download...";
|
return "Preparing download...";
|
||||||
case "downloadFetchInput":
|
case "downloadFetchInput":
|
||||||
return "Fetching input...";
|
return "Fetching input...";
|
||||||
|
case "downloadCheckInput":
|
||||||
|
return "Checking for empty input boxes...";
|
||||||
case "importing":
|
case "importing":
|
||||||
return "Importing...";
|
return "Importing...";
|
||||||
|
case "err_invalidConstraintFormat":
|
||||||
|
return "Error: Invalid constraint format.";
|
||||||
default:
|
default:
|
||||||
return input;
|
return input;
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-1
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
import React, { useState } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Box, Button, Output } from "./modules";
|
import { Box, Button, Output } from "./modules";
|
||||||
import { calculate_click, downloadLP } from "./scripts";
|
import { calculate_click, downloadLP, downloadMPS } from "./scripts";
|
||||||
import text from "./lang";
|
import text from "./lang";
|
||||||
|
|
||||||
export default function Home() {
|
export default function Home() {
|
||||||
@@ -24,6 +24,7 @@ export default function Home() {
|
|||||||
const tr_calc_max = text(language, "maximize");
|
const tr_calc_max = text(language, "maximize");
|
||||||
const tr_calc_min = text(language, "minimize");
|
const tr_calc_min = text(language, "minimize");
|
||||||
const tr_calcButton = text(language, "buttonCalc");
|
const tr_calcButton = text(language, "buttonCalc");
|
||||||
|
const tr_boxExportMPS = text(language, "boxExportMPS");
|
||||||
|
|
||||||
const handleLanguageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
const handleLanguageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
|
||||||
setLanguage(event.target.value);
|
setLanguage(event.target.value);
|
||||||
@@ -85,6 +86,10 @@ export default function Home() {
|
|||||||
className={"button"}
|
className={"button"}
|
||||||
onClickFunc={downloadLP}
|
onClickFunc={downloadLP}
|
||||||
/>
|
/>
|
||||||
|
<Button
|
||||||
|
title={tr_boxExportMPS}
|
||||||
|
className={"button"}
|
||||||
|
onClickFunc={downloadMPS} />
|
||||||
<br />
|
<br />
|
||||||
<Output
|
<Output
|
||||||
id="out"
|
id="out"
|
||||||
|
|||||||
+396
-12
@@ -1,7 +1,7 @@
|
|||||||
import * as MIP from "../parser/parseMIP"
|
import { LP } from "../solver/jvail/LP";
|
||||||
import * as LP from "../parser/parseLP"
|
import { Bound } from "../solver/jvail/Bound";
|
||||||
import * as LPAPI from "../api/optimizeLP.js"
|
import { Variable } from "../solver/jvail/Variable";
|
||||||
|
import { Bounds, GLP_MAX, GLP_MIN, GLP_UP, GLP_LO, GLP_FX, GLP_FR, GLP_DB } from "../solver/jvail/Bounds";
|
||||||
import * as GLPKAPI from "../solver/glpk.min.js"
|
import * as GLPKAPI from "../solver/glpk.min.js"
|
||||||
import { start } from "repl";
|
import { start } from "repl";
|
||||||
|
|
||||||
@@ -109,6 +109,30 @@ export function isInputValidRegex(obj: string | undefined, subj: string | undefi
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isInputFilled(obj: string | undefined, subj: string | undefined, bounds: string | undefined, vars: string | undefined): boolean {
|
export function isInputFilled(obj: string | undefined, subj: string | undefined, bounds: string | undefined, vars: string | undefined): boolean {
|
||||||
|
// if empty input: fetching inputs
|
||||||
|
if (obj == "" || subj == "" || bounds == "" || vars == "") {
|
||||||
|
const objectiveElement = document.getElementById('objective');
|
||||||
|
if (objectiveElement !== null) {
|
||||||
|
obj = (objectiveElement as HTMLInputElement).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const subjectElement = document.getElementById('subject');
|
||||||
|
if (subjectElement !== null) {
|
||||||
|
subj = (subjectElement as HTMLInputElement).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
const boundsElement = document.getElementById('bounds');
|
||||||
|
if (boundsElement !== null) {
|
||||||
|
bounds = (boundsElement as HTMLInputElement).value;
|
||||||
|
}
|
||||||
|
|
||||||
|
const varsElement = document.getElementById('vars');
|
||||||
|
if (varsElement !== null) {
|
||||||
|
vars = (varsElement as HTMLInputElement).value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (obj == "" || obj == null || obj == undefined) {
|
if (obj == "" || obj == null || obj == undefined) {
|
||||||
customLog("err_emptyBox");
|
customLog("err_emptyBox");
|
||||||
return false;
|
return false;
|
||||||
@@ -238,7 +262,7 @@ export function downloadLPFormatting(objective: any, subject: any, bounds: any)
|
|||||||
let operator = "Minimize";
|
let operator = "Minimize";
|
||||||
if (maxmin == "maximize") operator = "Maximize";
|
if (maxmin == "maximize") operator = "Maximize";
|
||||||
|
|
||||||
// Header mit Problemname
|
// Header with problem name
|
||||||
const header = "\\ Your problem\n";
|
const header = "\\ Your problem\n";
|
||||||
|
|
||||||
// format objective
|
// format objective
|
||||||
@@ -271,9 +295,19 @@ export function downloadLP() {
|
|||||||
customLogClear();
|
customLogClear();
|
||||||
customLog("downloadPrep");
|
customLog("downloadPrep");
|
||||||
customLog("");
|
customLog("");
|
||||||
customLog("downloadFetchInput");
|
customLog("downloadCheckInput");
|
||||||
customLog("");
|
customLog("");
|
||||||
|
|
||||||
|
if (!isInputFilled("","","","")) return;
|
||||||
|
|
||||||
|
let exportString: string | undefined = getInputsForLPAsString();
|
||||||
|
|
||||||
|
if (exportString === undefined) return;
|
||||||
|
|
||||||
|
downloadProblemDownload(exportString);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInputsForLP() {
|
||||||
let objective: string | undefined;
|
let objective: string | undefined;
|
||||||
const objectiveElement = document.getElementById('objective');
|
const objectiveElement = document.getElementById('objective');
|
||||||
if (objectiveElement !== null) {
|
if (objectiveElement !== null) {
|
||||||
@@ -298,10 +332,360 @@ export function downloadLP() {
|
|||||||
variables = (varsElement as HTMLInputElement).value;
|
variables = (varsElement as HTMLInputElement).value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// catch error: empty input field(s)
|
return { objective, subject, bounds };
|
||||||
if (!isInputFilled(objective, subject, bounds, variables)) return;
|
}
|
||||||
|
|
||||||
const exportString: string = downloadLPFormatting(objective, subject, bounds);
|
function getInputsForLPAsString(): string {
|
||||||
|
|
||||||
downloadProblemDownload(exportString);
|
let inputs = getInputsForLP();
|
||||||
|
let obj = inputs?.objective;
|
||||||
|
let sub = inputs?.subject;
|
||||||
|
let bnds = inputs?.bounds;
|
||||||
|
|
||||||
|
const exportString: string = downloadLPFormatting(obj, sub, bnds);
|
||||||
|
|
||||||
|
return exportString;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function convertLPToMPS(lp: LP): string {
|
||||||
|
let mpsString = '';
|
||||||
|
|
||||||
|
// NAME section
|
||||||
|
mpsString += `NAME ${lp.name}\n`;
|
||||||
|
|
||||||
|
// ROWS section
|
||||||
|
mpsString += 'ROWS\n';
|
||||||
|
mpsString += ` N ${lp.objective.name}\n`; // Objective row
|
||||||
|
lp.subjectTo.forEach(constraint => {
|
||||||
|
if (constraint.bounds.type === GLP_UP) { // <=
|
||||||
|
mpsString += ` L ${constraint.name}\n`;
|
||||||
|
} else if (constraint.bounds.type === GLP_LO) { // >=
|
||||||
|
mpsString += ` G ${constraint.name}\n`;
|
||||||
|
} else if (constraint.bounds.type === GLP_FX) { // =
|
||||||
|
mpsString += ` E ${constraint.name}\n`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// COLUMNS section
|
||||||
|
mpsString += 'COLUMNS\n';
|
||||||
|
const variableMap: { [key: string]: { row: string; coef: number }[] } = {};
|
||||||
|
lp.objective.vars.forEach(varObj => {
|
||||||
|
if (!variableMap[varObj.name]) {
|
||||||
|
variableMap[varObj.name] = [];
|
||||||
|
}
|
||||||
|
variableMap[varObj.name].push({ row: lp.objective.name, coef: varObj.coef });
|
||||||
|
});
|
||||||
|
lp.subjectTo.forEach(constraint => {
|
||||||
|
constraint.vars.forEach(varObj => {
|
||||||
|
if (!variableMap[varObj.name]) {
|
||||||
|
variableMap[varObj.name] = [];
|
||||||
|
}
|
||||||
|
variableMap[varObj.name].push({ row: constraint.name, coef: varObj.coef });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
for (const [variable, rows] of Object.entries(variableMap)) {
|
||||||
|
rows.forEach(entry => {
|
||||||
|
mpsString += ` ${variable} ${entry.row} ${entry.coef}\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// RHS section
|
||||||
|
mpsString += 'RHS\n';
|
||||||
|
lp.subjectTo.forEach(constraint => {
|
||||||
|
if (constraint.bounds.type === GLP_UP) { // <= or =
|
||||||
|
mpsString += ` RHS1 ${constraint.name} ${constraint.bounds.ub}\n`;
|
||||||
|
} else if (constraint.bounds.type === GLP_LO || constraint.bounds.type === GLP_FX) { // >=
|
||||||
|
mpsString += ` RHS1 ${constraint.name} ${constraint.bounds.lb}\n`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// BOUNDS section
|
||||||
|
if (lp.bounds && lp.bounds.length > 0) {
|
||||||
|
mpsString += 'BOUNDS\n';
|
||||||
|
lp.bounds.forEach(bound => {
|
||||||
|
if (bound.lb !== -Infinity) {
|
||||||
|
mpsString += ` LO BND1 ${bound.name} ${bound.lb}\n`;
|
||||||
|
}
|
||||||
|
if (bound.ub !== Infinity) {
|
||||||
|
mpsString += ` UP BND1 ${bound.name} ${bound.ub}\n`;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// BINARY section
|
||||||
|
if (lp.binaries && lp.binaries.length > 0) {
|
||||||
|
mpsString += 'BINARY\n';
|
||||||
|
lp.binaries.forEach(bin => {
|
||||||
|
mpsString += ` ${bin}\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// GENERAL section
|
||||||
|
if (lp.generals && lp.generals.length > 0) {
|
||||||
|
mpsString += 'GENERAL\n';
|
||||||
|
lp.generals.forEach(gen => {
|
||||||
|
mpsString += ` ${gen}\n`;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// ENDATA section
|
||||||
|
mpsString += 'ENDATA\n';
|
||||||
|
|
||||||
|
return mpsString;
|
||||||
|
}
|
||||||
|
|
||||||
|
// read LP format from string
|
||||||
|
function parseLP(lpString: string): LP {
|
||||||
|
const lines = lpString.split("\n").map((line) => line.trim()).filter((line) => line.length > 0);
|
||||||
|
let mode: string = '';
|
||||||
|
let lp: LP = {
|
||||||
|
name: '',
|
||||||
|
objective: {
|
||||||
|
direction: GLP_MAX, // 1 for maximize, -1 for minimize
|
||||||
|
name: '',
|
||||||
|
vars: []
|
||||||
|
},
|
||||||
|
subjectTo: [],
|
||||||
|
bounds: [],
|
||||||
|
binaries: [],
|
||||||
|
generals: []
|
||||||
|
};
|
||||||
|
|
||||||
|
let objectiveExpression = '';
|
||||||
|
let constraintExpression = '';
|
||||||
|
let currentConstraintName = '';
|
||||||
|
|
||||||
|
for (let line of lines) {
|
||||||
|
|
||||||
|
// handle each block differently
|
||||||
|
// set mode for each to determine parsing path
|
||||||
|
if (line.startsWith("Maximize")) {
|
||||||
|
lp.objective.direction = GLP_MAX;
|
||||||
|
mode = 'objective';
|
||||||
|
continue;
|
||||||
|
} else if (line.startsWith("Minimize")) {
|
||||||
|
lp.objective.direction = GLP_MIN;
|
||||||
|
mode = 'objective';
|
||||||
|
continue;
|
||||||
|
} else if (line.startsWith("Subject To")) {
|
||||||
|
// Constraint section
|
||||||
|
if (objectiveExpression.length > 0) {
|
||||||
|
lp.objective.vars = parseLPExpression(objectiveExpression.trim());
|
||||||
|
objectiveExpression = '';
|
||||||
|
}
|
||||||
|
mode = 'subjectTo';
|
||||||
|
continue;
|
||||||
|
} else if (line.startsWith("Bounds")) {
|
||||||
|
// Bound section
|
||||||
|
if (constraintExpression.length > 0 && currentConstraintName.length > 0) {
|
||||||
|
const { vars, bound } = parseLPConstraint(constraintExpression.trim());
|
||||||
|
lp.subjectTo.push({
|
||||||
|
name: currentConstraintName,
|
||||||
|
vars: vars,
|
||||||
|
bounds: bound
|
||||||
|
});
|
||||||
|
constraintExpression = '';
|
||||||
|
currentConstraintName = '';
|
||||||
|
}
|
||||||
|
mode = 'bounds';
|
||||||
|
continue;
|
||||||
|
} else if (line.startsWith("Binary")) {
|
||||||
|
mode = 'binaries';
|
||||||
|
continue;
|
||||||
|
} else if (line.startsWith("General")) {
|
||||||
|
mode = 'generals';
|
||||||
|
continue;
|
||||||
|
} else if (line.startsWith("End")) {
|
||||||
|
mode = 'end';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse based on current mode
|
||||||
|
if (mode === 'objective') {
|
||||||
|
const splitLine = line.split(":");
|
||||||
|
if (splitLine.length === 2) {
|
||||||
|
lp.objective.name = splitLine[0].trim(); // if name in first line
|
||||||
|
objectiveExpression += splitLine[1].trim() + ' ';
|
||||||
|
} else {
|
||||||
|
objectiveExpression += line.trim() + ' '; // multiline expansion of objective
|
||||||
|
}
|
||||||
|
|
||||||
|
} else if (mode === 'subjectTo') {
|
||||||
|
const splitLine = line.split(":");
|
||||||
|
if (splitLine.length === 2) {
|
||||||
|
if (constraintExpression.length > 0 && currentConstraintName.length > 0) {
|
||||||
|
// new Constraint -> add previous to the subjects
|
||||||
|
const { vars, bound } = parseLPConstraint(constraintExpression.trim());
|
||||||
|
lp.subjectTo.push({
|
||||||
|
name: currentConstraintName,
|
||||||
|
vars: vars,
|
||||||
|
bounds: bound
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// start collecting the new constraint
|
||||||
|
currentConstraintName = splitLine[0].trim();
|
||||||
|
constraintExpression = splitLine[1].trim() + ' ';
|
||||||
|
} else {
|
||||||
|
// continue collecting the expression if it's multi-line
|
||||||
|
constraintExpression += line.trim() + ' ';
|
||||||
|
}
|
||||||
|
} else if (mode === 'bounds') {
|
||||||
|
const bound = parseLPBound(line.trim());
|
||||||
|
if (bound && lp.bounds) {
|
||||||
|
lp.bounds.push(bound);
|
||||||
|
}
|
||||||
|
} else if (mode === 'binaries') {
|
||||||
|
lp.binaries?.push(line.trim());
|
||||||
|
} else if (mode === 'generals') {
|
||||||
|
lp.generals?.push(line.trim());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// if no "subject to" previously, do it here
|
||||||
|
if (objectiveExpression.length > 0) {
|
||||||
|
lp.objective.vars = parseLPExpression(objectiveExpression.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
// same for "bounds" and "end"
|
||||||
|
if (constraintExpression.length > 0 && currentConstraintName.length > 0) {
|
||||||
|
const { vars, bound } = parseLPConstraint(constraintExpression.trim());
|
||||||
|
lp.subjectTo.push({
|
||||||
|
name: currentConstraintName,
|
||||||
|
vars: vars,
|
||||||
|
bounds: bound
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return lp;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for expressions
|
||||||
|
function parseLPExpression(expr: string): Variable[] {
|
||||||
|
const regex = /([+-]?\s*\d*\.?\d*)\s*([a-zA-Z_][a-zA-Z_0-9]*)/g;
|
||||||
|
let match;
|
||||||
|
let vars: Variable[] = [];
|
||||||
|
|
||||||
|
// loop over all variables in expresion
|
||||||
|
while ((match = regex.exec(expr)) !== null) {
|
||||||
|
let temp_coef = match[1].replace(/\s+/g, '').trim() || '1';
|
||||||
|
temp_coef = temp_coef === "-" || temp_coef === '+' ? `${temp_coef}1` : temp_coef;
|
||||||
|
const coef = parseFloat(temp_coef);
|
||||||
|
const name = match[2];
|
||||||
|
vars.push({ name: name, coef: coef });
|
||||||
|
}
|
||||||
|
|
||||||
|
return vars;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for Constraint section
|
||||||
|
function parseLPConstraint(constraint: string): { vars: Variable[], bound: Bounds } {
|
||||||
|
// Valid operators in Constraints
|
||||||
|
const operators = ["<=", ">=", "="];
|
||||||
|
let operator = operators.find(op => constraint.includes(op));
|
||||||
|
if (!operator) {
|
||||||
|
throw new Error(getTranslation("err_invalidConstraintFormat"));
|
||||||
|
}
|
||||||
|
|
||||||
|
const [expr, boundStr] = constraint.split(operator);
|
||||||
|
const vars: Variable[] = parseLPExpression(expr.trim());
|
||||||
|
const boundValue = parseFloat(boundStr.trim());
|
||||||
|
|
||||||
|
// determine bound type
|
||||||
|
let boundType = GLP_UP;
|
||||||
|
if (operator === "<=") {
|
||||||
|
boundType = GLP_UP;
|
||||||
|
} else if (operator === ">=") {
|
||||||
|
boundType = GLP_LO;
|
||||||
|
} else if (operator === "=") {
|
||||||
|
boundType = GLP_FX;
|
||||||
|
}
|
||||||
|
let lb = boundType === GLP_FX ? boundValue : boundType === GLP_LO ? boundValue : -Infinity;
|
||||||
|
let ub = boundType === GLP_UP ? boundValue : Infinity;
|
||||||
|
|
||||||
|
const bound: Bounds = {
|
||||||
|
type: boundType,
|
||||||
|
lb: lb,
|
||||||
|
ub: ub
|
||||||
|
} as Bounds;
|
||||||
|
|
||||||
|
return { vars, bound };
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper for Bound section
|
||||||
|
function parseLPBound(boundStr: string): Bound | null {
|
||||||
|
// Regex to handle various bound formats
|
||||||
|
const regex = /^([-]?\d*\.?\d*)?\s*(<=|>=|=)?\s*([a-zA-Z_][a-zA-Z_0-9]*)\s*(<=|>=|=)?\s*([-]?\d*\.?\d*)?$/;
|
||||||
|
const match = regex.exec(boundStr.trim());
|
||||||
|
|
||||||
|
if (match) {
|
||||||
|
const [, lbStr, leftOperator, varName, rightOperator, ubStr] = match;
|
||||||
|
let lb = lbStr ? parseFloat(lbStr) : undefined;
|
||||||
|
let ub = ubStr ? parseFloat(ubStr) : undefined;
|
||||||
|
let type: number;
|
||||||
|
|
||||||
|
// Handle free "edgecase"
|
||||||
|
if (boundStr.toLowerCase().includes('free')) {
|
||||||
|
return {
|
||||||
|
type: GLP_FR,
|
||||||
|
name: varName,
|
||||||
|
lb: -Infinity,
|
||||||
|
ub: Infinity
|
||||||
|
} as Bound;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine bound type
|
||||||
|
if (leftOperator && rightOperator) {
|
||||||
|
if (leftOperator === '<=' && rightOperator === '<=') {
|
||||||
|
type = GLP_DB; // Double bound
|
||||||
|
} else if (leftOperator === '>=' && rightOperator === '>=') {
|
||||||
|
type = GLP_DB; // Double bound (reverse order)
|
||||||
|
[lb, ub] = [ub, lb]; // Swap lb and ub
|
||||||
|
} else {
|
||||||
|
return null; // Invalid combination
|
||||||
|
}
|
||||||
|
// detect one-sided bounds
|
||||||
|
} else if (leftOperator === '<=') {
|
||||||
|
type = GLP_UP;
|
||||||
|
ub = lb;
|
||||||
|
lb = undefined;
|
||||||
|
} else if (rightOperator === '<=') {
|
||||||
|
type = GLP_UP;
|
||||||
|
} else if (leftOperator === '>=') {
|
||||||
|
type = GLP_LO;
|
||||||
|
} else if (rightOperator === '>=') {
|
||||||
|
type = GLP_LO;
|
||||||
|
lb = ub;
|
||||||
|
ub = undefined;
|
||||||
|
} else if (leftOperator === '=' || rightOperator === '=') {
|
||||||
|
type = GLP_FX;
|
||||||
|
if (leftOperator === '=') ub = lb;
|
||||||
|
else lb = ub;
|
||||||
|
} else {
|
||||||
|
type = GLP_FR; // No bounds specified, assume free
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
type,
|
||||||
|
name: varName,
|
||||||
|
lb: lb !== undefined ? lb : -Infinity,
|
||||||
|
ub: ub !== undefined ? ub : Infinity
|
||||||
|
} as Bound;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function downloadMPS() {
|
||||||
|
customLogClear();
|
||||||
|
customLog("downloadPrep");
|
||||||
|
customLog("");
|
||||||
|
customLog("downloadCheckInput");
|
||||||
|
customLog("");
|
||||||
|
if (!isInputFilled("","","","")) return;
|
||||||
|
|
||||||
|
let inputs = getInputsForLPAsString();
|
||||||
|
let lp = parseLP(inputs);
|
||||||
|
let mps = convertLPToMPS(lp);
|
||||||
|
|
||||||
|
downloadProblemDownload(mps);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export interface Bound{
|
||||||
|
name: string;
|
||||||
|
type: number; // 1 for lower bound, 2 for upper bound, 3 for equal
|
||||||
|
ub: number;
|
||||||
|
lb: number;
|
||||||
|
}
|
||||||
@@ -0,0 +1,9 @@
|
|||||||
|
export interface Bounds{ type: number, ub: number, lb: number }
|
||||||
|
|
||||||
|
export const GLP_FR = 1; /* free (unbounded) variable */
|
||||||
|
export const GLP_LO = 2; /* variable with lower bound */
|
||||||
|
export const GLP_UP = 3; /* variable with upper bound */
|
||||||
|
export const GLP_DB = 4; /* double-bounded variable */
|
||||||
|
export const GLP_FX = 5; /* fixed variable */
|
||||||
|
export const GLP_MAX = 2;
|
||||||
|
export const GLP_MIN = 1
|
||||||
@@ -0,0 +1,8 @@
|
|||||||
|
import {Variable} from "./Variable";
|
||||||
|
import {Bounds} from "./Bounds";
|
||||||
|
|
||||||
|
export interface Constraint {
|
||||||
|
name: string;
|
||||||
|
vars: Variable[];
|
||||||
|
bounds: Bounds;
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
import {Variable} from "./Variable";
|
||||||
|
import {Bound} from "./Bound";
|
||||||
|
import {Options} from "./Options";
|
||||||
|
import {Constraint} from "./Constraint";
|
||||||
|
|
||||||
|
export interface LP {
|
||||||
|
name: string,
|
||||||
|
objective: {
|
||||||
|
direction: number,
|
||||||
|
name: string,
|
||||||
|
vars: Variable[]
|
||||||
|
},
|
||||||
|
subjectTo: Constraint[],
|
||||||
|
bounds?: Bound[],
|
||||||
|
binaries?: string[],
|
||||||
|
generals?: string[],
|
||||||
|
options?: Options
|
||||||
|
}
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import {Result} from "./Result";
|
||||||
|
|
||||||
|
export interface Options {
|
||||||
|
mipgap?: number, /* set relative mip gap tolerance to mipgap, default 0.0 */
|
||||||
|
tmlim?: number, /* limit solution time to tmlim seconds, default INT_MAX */
|
||||||
|
msglev?: number, /* message level for terminal output, default GLP_MSG_ERR */
|
||||||
|
presol?: boolean, /* use presolver, default true */
|
||||||
|
cb?: { /* a callback called at each 'each' iteration (only simplex) */
|
||||||
|
call(result: Result):Result,
|
||||||
|
each: number
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export interface Result {
|
||||||
|
name: string;
|
||||||
|
time: number;
|
||||||
|
result: {
|
||||||
|
status: number;
|
||||||
|
z: number;
|
||||||
|
vars: {[key:string]: number};
|
||||||
|
dual?: { [key: string]: number }; /* simplex only */
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,4 @@
|
|||||||
|
export interface Variable {
|
||||||
|
name: string;
|
||||||
|
coef: number
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user