mergen von GMPL und main #51

Closed
SinusFox wants to merge 5 commits from main into merge_GMPL_main
24 changed files with 6065 additions and 238 deletions
-3
View File
@@ -12,9 +12,6 @@ on:
# Allows you to run this workflow manually from the Actions tab
workflow_dispatch:
# Automatically run on Pull Request
pull_request:
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
permissions:
contents: read
+62
View File
@@ -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
+26
View File
@@ -0,0 +1,26 @@
name: Run Tests
on:
push:
branches: ["main"]
workflow_dispatch:
pull_request:
jobs:
test:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@v2
- name: Set up Node.js
uses: actions/setup-node@v2
with:
node-version: '20'
- name: Install dependencies
run: npm install
- name: Run tests
run: npm run test:ci
+362
View File
@@ -0,0 +1,362 @@
Mozilla Public License, version 2.0
1. Definitions
1.1. "Contributor"
means each individual or legal entity that creates, contributes to the
creation of, or owns Covered Software.
1.2. "Contributor Version"
means the combination of the Contributions of others (if any) used by a
Contributor and that particular Contributor's Contribution.
1.3. "Contribution"
means Covered Software of a particular Contributor.
1.4. "Covered Software"
means Source Code Form to which the initial Contributor has attached the
notice in Exhibit A, the Executable Form of such Source Code Form, and
Modifications of such Source Code Form, in each case including portions
thereof.
1.5. "Incompatible With Secondary Licenses"
means
a. that the initial Contributor has attached the notice described in
Exhibit B to the Covered Software; or
b. that the Covered Software was made available under the terms of
version 1.1 or earlier of the License, but not also under the terms of
a Secondary License.
1.6. "Executable Form"
means any form of the work other than Source Code Form.
1.7. "Larger Work"
means a work that combines Covered Software with other material, in a
separate file or files, that is not Covered Software.
1.8. "License"
means this document.
1.9. "Licensable"
means having the right to grant, to the maximum extent possible, whether
at the time of the initial grant or subsequently, any and all of the
rights conveyed by this License.
1.10. "Modifications"
means any of the following:
a. any file in Source Code Form that results from an addition to,
deletion from, or modification of the contents of Covered Software; or
b. any new file in Source Code Form that contains any Covered Software.
1.11. "Patent Claims" of a Contributor
means any patent claim(s), including without limitation, method,
process, and apparatus claims, in any patent Licensable by such
Contributor that would be infringed, but for the grant of the License,
by the making, using, selling, offering for sale, having made, import,
or transfer of either its Contributions or its Contributor Version.
1.12. "Secondary License"
means either the GNU General Public License, Version 2.0, the GNU Lesser
General Public License, Version 2.1, the GNU Affero General Public
License, Version 3.0, or any later versions of those licenses.
1.13. "Source Code Form"
means the form of the work preferred for making modifications.
1.14. "You" (or "Your")
means an individual or a legal entity exercising rights under this
License. For legal entities, "You" includes any entity that controls, is
controlled by, or is under common control with You. For purposes of this
definition, "control" means (a) the power, direct or indirect, to cause
the direction or management of such entity, whether by contract or
otherwise, or (b) ownership of more than fifty percent (50%) of the
outstanding shares or beneficial ownership of such entity.
2. License Grants and Conditions
2.1. Grants
Each Contributor hereby grants You a world-wide, royalty-free,
non-exclusive license:
a. under intellectual property rights (other than patent or trademark)
Licensable by such Contributor to use, reproduce, make available,
modify, display, perform, distribute, and otherwise exploit its
Contributions, either on an unmodified basis, with Modifications, or
as part of a Larger Work; and
b. under Patent Claims of such Contributor to make, use, sell, offer for
sale, have made, import, and otherwise transfer either its
Contributions or its Contributor Version.
2.2. Effective Date
The licenses granted in Section 2.1 with respect to any Contribution
become effective for each Contribution on the date the Contributor first
distributes such Contribution.
2.3. Limitations on Grant Scope
The licenses granted in this Section 2 are the only rights granted under
this License. No additional rights or licenses will be implied from the
distribution or licensing of Covered Software under this License.
Notwithstanding Section 2.1(b) above, no patent license is granted by a
Contributor:
a. for any code that a Contributor has removed from Covered Software; or
b. for infringements caused by: (i) Your and any other third party's
modifications of Covered Software, or (ii) the combination of its
Contributions with other software (except as part of its Contributor
Version); or
c. under Patent Claims infringed by Covered Software in the absence of
its Contributions.
This License does not grant any rights in the trademarks, service marks,
or logos of any Contributor (except as may be necessary to comply with
the notice requirements in Section 3.4).
2.4. Subsequent Licenses
No Contributor makes additional grants as a result of Your choice to
distribute the Covered Software under a subsequent version of this
License (see Section 10.2) or under the terms of a Secondary License (if
permitted under the terms of Section 3.3).
2.5. Representation
Each Contributor represents that the Contributor believes its
Contributions are its original creation(s) or it has sufficient rights to
grant the rights to its Contributions conveyed by this License.
2.6. Fair Use
This License is not intended to limit any rights You have under
applicable copyright doctrines of fair use, fair dealing, or other
equivalents.
2.7. Conditions
Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
Section 2.1.
3. Responsibilities
3.1. Distribution of Source Form
All distribution of Covered Software in Source Code Form, including any
Modifications that You create or to which You contribute, must be under
the terms of this License. You must inform recipients that the Source
Code Form of the Covered Software is governed by the terms of this
License, and how they can obtain a copy of this License. You may not
attempt to alter or restrict the recipients' rights in the Source Code
Form.
3.2. Distribution of Executable Form
If You distribute Covered Software in Executable Form then:
a. such Covered Software must also be made available in Source Code Form,
as described in Section 3.1, and You must inform recipients of the
Executable Form how they can obtain a copy of such Source Code Form by
reasonable means in a timely manner, at a charge no more than the cost
of distribution to the recipient; and
b. You may distribute such Executable Form under the terms of this
License, or sublicense it under different terms, provided that the
license for the Executable Form does not attempt to limit or alter the
recipients' rights in the Source Code Form under this License.
3.3. Distribution of a Larger Work
You may create and distribute a Larger Work under terms of Your choice,
provided that You also comply with the requirements of this License for
the Covered Software. If the Larger Work is a combination of Covered
Software with a work governed by one or more Secondary Licenses, and the
Covered Software is not Incompatible With Secondary Licenses, this
License permits You to additionally distribute such Covered Software
under the terms of such Secondary License(s), so that the recipient of
the Larger Work may, at their option, further distribute the Covered
Software under the terms of either this License or such Secondary
License(s).
3.4. Notices
You may not remove or alter the substance of any license notices
(including copyright notices, patent notices, disclaimers of warranty, or
limitations of liability) contained within the Source Code Form of the
Covered Software, except that You may alter any license notices to the
extent required to remedy known factual inaccuracies.
3.5. Application of Additional Terms
You may choose to offer, and to charge a fee for, warranty, support,
indemnity or liability obligations to one or more recipients of Covered
Software. However, You may do so only on Your own behalf, and not on
behalf of any Contributor. You must make it absolutely clear that any
such warranty, support, indemnity, or liability obligation is offered by
You alone, and You hereby agree to indemnify every Contributor for any
liability incurred by such Contributor as a result of warranty, support,
indemnity or liability terms You offer. You may include additional
disclaimers of warranty and limitations of liability specific to any
jurisdiction.
4. Inability to Comply Due to Statute or Regulation
If it is impossible for You to comply with any of the terms of this License
with respect to some or all of the Covered Software due to statute,
judicial order, or regulation then You must: (a) comply with the terms of
this License to the maximum extent possible; and (b) describe the
limitations and the code they affect. Such description must be placed in a
text file included with all distributions of the Covered Software under
this License. Except to the extent prohibited by statute or regulation,
such description must be sufficiently detailed for a recipient of ordinary
skill to be able to understand it.
5. Termination
5.1. The rights granted under this License will terminate automatically if You
fail to comply with any of its terms. However, if You become compliant,
then the rights granted under this License from a particular Contributor
are reinstated (a) provisionally, unless and until such Contributor
explicitly and finally terminates Your grants, and (b) on an ongoing
basis, if such Contributor fails to notify You of the non-compliance by
some reasonable means prior to 60 days after You have come back into
compliance. Moreover, Your grants from a particular Contributor are
reinstated on an ongoing basis if such Contributor notifies You of the
non-compliance by some reasonable means, this is the first time You have
received notice of non-compliance with this License from such
Contributor, and You become compliant prior to 30 days after Your receipt
of the notice.
5.2. If You initiate litigation against any entity by asserting a patent
infringement claim (excluding declaratory judgment actions,
counter-claims, and cross-claims) alleging that a Contributor Version
directly or indirectly infringes any patent, then the rights granted to
You by any and all Contributors for the Covered Software under Section
2.1 of this License shall terminate.
5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
license agreements (excluding distributors and resellers) which have been
validly granted by You or Your distributors under this License prior to
termination shall survive termination.
6. Disclaimer of Warranty
Covered Software is provided under this License on an "as is" basis,
without warranty of any kind, either expressed, implied, or statutory,
including, without limitation, warranties that the Covered Software is free
of defects, merchantable, fit for a particular purpose or non-infringing.
The entire risk as to the quality and performance of the Covered Software
is with You. Should any Covered Software prove defective in any respect,
You (not any Contributor) assume the cost of any necessary servicing,
repair, or correction. This disclaimer of warranty constitutes an essential
part of this License. No use of any Covered Software is authorized under
this License except under this disclaimer.
7. Limitation of Liability
Under no circumstances and under no legal theory, whether tort (including
negligence), contract, or otherwise, shall any Contributor, or anyone who
distributes Covered Software as permitted above, be liable to You for any
direct, indirect, special, incidental, or consequential damages of any
character including, without limitation, damages for lost profits, loss of
goodwill, work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses, even if such party shall have been
informed of the possibility of such damages. This limitation of liability
shall not apply to liability for death or personal injury resulting from
such party's negligence to the extent applicable law prohibits such
limitation. Some jurisdictions do not allow the exclusion or limitation of
incidental or consequential damages, so this exclusion and limitation may
not apply to You.
8. Litigation
Any litigation relating to this License may be brought only in the courts
of a jurisdiction where the defendant maintains its principal place of
business and such litigation shall be governed by laws of that
jurisdiction, without reference to its conflict-of-law provisions. Nothing
in this Section shall prevent a party's ability to bring cross-claims or
counter-claims.
9. Miscellaneous
This License represents the complete agreement concerning the subject
matter hereof. If any provision of this License is held to be
unenforceable, such provision shall be reformed only to the extent
necessary to make it enforceable. Any law or regulation which provides that
the language of a contract shall be construed against the drafter shall not
be used to construe this License against a Contributor.
10. Versions of the License
10.1. New Versions
Mozilla Foundation is the license steward. Except as provided in Section
10.3, no one other than the license steward has the right to modify or
publish new versions of this License. Each version will be given a
distinguishing version number.
10.2. Effect of New Versions
You may distribute the Covered Software under the terms of the version
of the License under which You originally received the Covered Software,
or under the terms of any subsequent version published by the license
steward.
10.3. Modified Versions
If you create software not governed by this License, and you want to
create a new license for such software, you may create and use a
modified version of this License if you rename the license and remove
any references to the name of the license steward (except to note that
such modified license differs from this License).
10.4. Distributing Source Code Form that is Incompatible With Secondary
Licenses If You choose to distribute Source Code Form that is
Incompatible With Secondary Licenses under the terms of this version of
the License, the notice described in Exhibit B of this License must be
attached.
Exhibit A - Source Code Form License Notice
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
If it is not possible or desirable to put the notice in a particular file,
then You may include the notice in a location (such as a LICENSE file in a
relevant directory) where a recipient would be likely to look for such a
notice.
You may add additional accurate notices of copyright ownership.
Exhibit B - "Incompatible With Secondary Licenses" Notice
This Source Code Form is "Incompatible
With Secondary Licenses", as defined by
the Mozilla Public License, v. 2.0.
+29 -20
View File
@@ -3,57 +3,66 @@ This projects aims to create a tool for easy calculation of operations research
## Table of Contents
- [Features](#features)
- [Installation](#installation)
- [Installation/Access](#installationaccess)
- [Usage](#usage)
- [Supported problem Types](#supported-problem-types)
- [Supported problem types](#supported-problem-types)
- [Contributing](#contributing)
- [Licence](#licence)
- [Contact](#contact)
- [Troubleshooting](#troubleshooting)
## Features
ToDo
## Installation
### On web
You can always use the OR-Tool [without any installation](https://spaceholder-programming.github.io/Operations-Research-Tool/).
- Export as LP (Linear Programming)
- Measuring elapsed real time
- Logging
- Solving via GLPK and HiGHS
## Installation/Access
### Online
You can always access the Tool without any installation on our [GitHub Pages instance](https://spaceholder-programming.github.io/Operations-Research-Tool/).
### Local
1. Install dependencies:
#### Install dependencies
This project relies on [NextJs](https://nextjs.org/). Please follow its [installation instructions](https://nextjs.org/docs/getting-started/installation) to get everything ready.
2. Clone the repository:
#### Clone the repository
Using Git:
```Bash
git clone https://github.com/Spaceholder-Programming/Operations-Research-Tool.git
```
3. Build the site:
Open the folder where the project was saved in PowerShell (or your favorite console). Then build the site:
#### Building the site
Navigate towards the folder, where the project is located on your machine via terminal.
Afterwards, execute the following command:
```Bash
npm build
```
4. Run it:
#### Run
```
npm start
```
5. Access the OR-Tool using your browser:
Usually it starts on port 3000. [This link](http://localhost:3000) should work. Otherwise check your console for the link.
## Usage
#### Access the Tool using your browser:
You can access the tool via browser on your machine. The default port is 3000.
If you can not reach the tool under [this link](http://localhost:3000), the default port is blocked and you have to check the terminal to get the correct port.
## Usage
ToDo
### Supported problem Types
### Supported problem types
+ Linear
+ Mixed Integer
## Contributing
1. Fork the repository
2. Create a new branch: `git checkout -b Featurename`
2. Create a new branch: `git checkout -b featurename`
3. Implement your changes
4. Push your branch: `git push origin featurename`
5. Create a pull request
# Licence
For further information, please check out the [LICENSE](https://github.com/Spaceholder-Programming/Operations-Research-Tool/blob/main/LICENCE.txt).
=======
This project is licensed under the [MIT License](https://github.com/Spaceholder-Programming/Operations-Research-Tool?tab=MIT-1-ov-file).
# Contact
If you have the desire to contact the team behind this project, use the contact details on our GitHub accounts:
+ [bRNS98](https://github.com/bRNS98)
+ [moebiusl](https://giothub.com/moebiusl)
+ [moebiusl](https://github.com/moebiusl)
+ [SinusFox](https://github.com/SinusFox)
+ [widepoeppihappy](https://github.com/widepoeppihappy)
# Troubleshooting
If you find erros in the code, please contact us by [creating an issue](https://github.com/Spaceholder-Programming/Operations-Research-Tool/issues/new).
If you find bug, please contact us by [creating an issue](https://github.com/Spaceholder-Programming/Operations-Research-Tool/issues/new).
+22
View File
@@ -0,0 +1,22 @@
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
import '@testing-library/jest-dom';
import Home from "../src/app/page";
import { customLog, customLogClear } from '../src/app/scripts';
jest.mock('../src/app/scripts', () => ({
customLog: jest.fn(),
customLogClear: jest.fn(),
}));
jest.mock('../src/solver/glpk.min.js', () => ({
LPF_ECOND: 2,
}));
test('render home page', () => {
// render website
render(<Home />);
// check if text is in document
const headingElement = screen.getByText(/OR-Tool/i); // text search in document
expect(headingElement).toBeInTheDocument();
});
+104
View File
@@ -0,0 +1,104 @@
import { render, fireEvent, screen } from '@testing-library/react';
import {
customLog,
customLogClear,
getTranslation,
isInputValidRegex,
isInputFilled,
downloadLPFormatting,
downloadLP,
calculate_click
} from '../src/app/scripts';
import Home from '../src/app/page'
import text from '../src/app/lang';
// Mocking GLPKAPI and console log
jest.mock('../src/solver/glpk.min.js', () => ({
LPF_ECOND: 2,
}));
// Mocking console.log
const consoleLogMock = jest.spyOn(console, 'log').mockImplementation(() => {});
beforeEach(() => {
document.body.innerHTML = `
<div>
<select id="language_current">
<option value="eng">English</option>
</select>
<textarea id="objective"></textarea>
<textarea id="subject"></textarea>
<textarea id="bounds"></textarea>
<textarea id="vars"></textarea>
<select id="maxminswitch">
<option value="maximize">Maximize</option>
<option value="minimize">Minimize</option>
</select>
<div id="out"></div>
</div>
`;
jest.clearAllMocks(); // Clear any previous mocks
});
test('customLog should append message to output box', () => {
const message = 'Test message';
customLog(message);
const outputElement = document.getElementById('out');
expect(outputElement.innerHTML).toContain(message);
});
test('customLogClear should clear the output box', () => {
const message = 'Test message';
customLog(message);
customLogClear();
const outputElement = document.getElementById('out');
expect(outputElement.innerHTML).toBe('');
});
test('getTranslation should return translation based on selected language', () => {
const result = getTranslation('header_title');
expect(result).toBe(text('eng', 'header_title')); // Assuming text function provides correct translation
});
test('isInputValidRegex should validate input regex correctly', () => {
expect(isInputValidRegex("x + y", "+1 x + 2 y <= 15\n+3 x + 1 y <= 20", "x >= 0\ny >= 0", "x\ny")).toBe(true);
expect(isInputValidRegex("x + y", "+1 x + 2 y <= 15\n+3 x + 1 y <= 20", "x >= 0\ny >= 0", "")).toBe(false); // Invalid objective
});
test('isInputFilled should check for filled inputs', () => {
expect(isInputFilled('3x + 5y', 'x + y <= 10', 'x <= 5', 'x\ny')).toBe(true);
expect(isInputFilled('', 'x + y <= 10', 'x <= 5', 'x\ny')).toBe(false); // Objective empty
});
test('downloadLPFormatting should format LP correctly', () => {
const formattedLP = downloadLPFormatting('3x + 5y', 'x + y <= 10', 'x <= 5');
expect(formattedLP).toContain('obj: 3x + 5y');
expect(formattedLP).toContain('Subject To');
expect(formattedLP).toContain('Bounds');
});
test('calculate_click should display "Calculating" in the output box', () => {
render(<Home />);
// Spy on customLog and customLogClear to prevent actual logging and check the calls
const mockClear = jest.spyOn({ customLogClear }, 'customLogClear').mockImplementation();
const mockLog = jest.spyOn({ customLog }, 'customLog').mockImplementation();
// Set valid inputs
document.getElementById('objective').value = '3x + 5y';
document.getElementById('subject').value = 'x + y <= 10';
document.getElementById('bounds').value = 'x <= 5';
document.getElementById('vars').value = 'x\ny';
// Simuliere den Button-Klick, der die Berechnung startet
fireEvent.click(screen.getByText('Calculate'));
// Check the contents of out box
const outputElement = document.getElementById('out');
expect(outputElement.innerHTML).toContain('Calculating');
// Clear mock
mockClear.mockRestore();
mockLog.mockRestore();
});
+15
View File
@@ -0,0 +1,15 @@
const nextJest = require('next/jest');
const createJestConfig = nextJest({
dir: './',
});
const customJestConfig = {
testEnvironment: 'jest-environment-jsdom',
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/src/$1',
},
setupFilesAfterEnv: ['<rootDir>/jest.setup.js'],
};
module.exports = createJestConfig(customJestConfig);
+1
View File
@@ -0,0 +1 @@
import '@testing-library/jest-dom';
+4562 -27
View File
File diff suppressed because it is too large Load Diff
+13 -1
View File
@@ -6,22 +6,34 @@
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
"lint": "next lint",
"test": "jest --watchAll",
"test:ci": "jest --ci --coverage"
},
"dependencies": {
"glpk.js": "^4.0.2",
"i18n": "^0.15.1",
"i18next": "^23.15.2",
"i18next-browser-languagedetector": "^8.0.0",
"jest-environment-jsdom": "^29.7.0",
"next": "14.2.11",
"next-i18next": "^15.3.1",
"react": "^18",
"react-dom": "^18",
"react-i18next": "^15.0.2",
"react-popup": "^0.11.2",
"reactjs-popup": "^2.0.6"
},
"devDependencies": {
"@testing-library/jest-dom": "^6.5.0",
"@testing-library/react": "^16.0.1",
"@testing-library/user-event": "^14.5.2",
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"eslint": "^8",
"eslint-config-next": "14.2.11",
"jest": "^29.7.0",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
+30 -2
View File
@@ -3,8 +3,8 @@
@tailwind utilities;
:root {
--background: #ffffff;
--foreground: #171717;
--background: #171717;
--foreground: #ffffff;
}
@media (prefers-color-scheme: dark) {
@@ -158,3 +158,31 @@ body {
.popup-overlay {
background: rgba(0, 0, 0, 0.5);
}
.dropdown-custom {
width: 150px;
background-color: black;
color: white;
border: none;
padding: 10px;
border-radius: 20px;
margin: 10px;
font-size: 16px;
cursor: pointer;
}
.dropdown-custom:hover {
background-color: #444;
}
.dropdown-custom-maxmin {
width: 150px;
background-color: black;
color: white;
border: 2px solid #5353535c;
padding: 10px;
border-radius: 20px;
margin: 10px;
font-size: 16px;
cursor: pointer;
}
+216
View File
@@ -0,0 +1,216 @@
export default function text(lang: string, input: string): string {
// German translation
if (lang === "ger") {
switch (input) {
case "header_title":
return "OR-Tool";
case "header_subtitle":
return "von Spaceholder Programming";
case "boxObjTitle":
return "Ziel";
case "boxObjDesc":
return "Geben Sie Ihr Ziel hier ein. Es ist nur ein Ziel erlaubt. Verwenden Sie eine Zeile dafür (kein 'Enter'!). Erlaubte Symbole sind 0-9, a-z, A-Z und <>=.\nBeispiel:\nx + y\n-786433 x1 + 655361 x2";
case "boxSubjTitle":
return "Nebenbedingungen";
case "boxSubjDesc":
return "Geben Sie Ihre Nebenbedingungen hier ein. Eine pro Zeile (mit der 'Enter'-Taste trennen). Erlaubte Symbole sind 0-9, a-z, A-Z und <>=.\nBeispiel:\n+1 x + 2 y <= 15\n524321 x14 + 524305 x15 <= 4194303.5";
case "boxBoundsTitle":
return "Grenzen";
case "boxBoundsDesc":
return "Geben Sie Ihre Grenzen hier ein. Eine pro Zeile (mit der 'Enter'-Taste trennen). Erlaubte Symbole sind 0-9, a-z, A-Z und <>=.\nBeispiel:\nx >= 0\nx > 0\n0 <= x1 <= 1";
case "boxVarsTitle":
return "Variablen";
case "boxVarsDesc":
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":
return "Als LP exportieren";
case "boxExportMPS":
return "Als MPS exportieren";
case "boxOut":
return "Geben Sie ein Problem ein und drücken Sie eine Aktionstaste, um die Ausgabe anzuzeigen...";
case "buttonCalc":
return "Berechnen";
case "etime":
return "Berechnungsdauer";
case "seconds":
return "Sekunden";
case "err_invalidInput":
return "Fehler: Ungültige Eingabe in";
case "err_nullInput":
return "Fehler: NULL- oder undefinierte Eingabe in";
case "err_invalidInput":
return "Fehler: Ungültige Eingabe oder fehlendes Zeichen in";
case "input_checks_successful":
return "Alle Eingabeprüfungen erfolgreich.";
case "input_checks_start":
return "Starte Eingabeprüfungen...";
case "obj_box":
return "Zielfeld";
case "subj_box":
return "Bedingungsfeld";
case "bounds_box":
return "Grenzenfeld";
case "vars_box":
return "Variablenfeld";
case "err_emptyBox":
return "Fehler: Leeres Textfeld.";
case "calculating":
return "Berechne...";
case "maximize":
return "Maximieren";
case "minimize":
return "Minimieren";
case "run_optimization":
return "Optimierung mit Eingaben wird ausgeführt";
case "startProblemSetup":
return "Starte Problemerstellung...";
case "succProblemSetup":
return "Problem erstellt.";
case "startScaling":
return "Skalieren des Problems...";
case "succScaling":
return "Skalierung erfolgreich.";
case "startOptimizationSimplex":
return "Starte Simplex-Optimierung...";
case "succOptimizationSimplex":
return "Simplex-Optimierung abgeschlossen.";
case "startOptimizationInteger":
return "Starte Ganzzahloptimierung...";
case "succOptimizationInteger":
return "Ganzzahloptimierung abgeschlossen.";
case "finalObjValue":
return "Endgültiger Zielfunktionswert";
case "varsValues":
return "Wert jeder Variable";
case "dualValues":
return "Dualwerte der Einschränkungen";
case "downloadPrepFileString":
return "Dateiinhalt wird vorbereitet...";
case "downloadPrepFile":
return "Datei wird vorbereitet...";
case "downloadStart":
return "Download wird gestartet.";
case "downloadPrep":
return "Download wird vorbereitet...";
case "downloadFetchInput":
return "Eingaben werden geladen...";
case "downloadCheckInput":
return "Überprüfe auf leere Eingabefelder...";
case "importing":
return "Importiere...";
case "err_invalidConstraintFormat":
return "Fehler: Nicht erlaubter Operator verwendet.";
default:
return input;
}
}
// English translation
if (lang === "eng") {
switch (input) {
case "header_title":
return "OR-Tool";
case "header_subtitle":
return "by Spaceholder Programming";
case "boxObjTitle":
return "Objective";
case "boxObjDesc":
return "Insert your objective here. One objective is allowed. Use one line for it (no \"return\"!) Allowed symbols are 0-9, a-z, A-Z and <>=.\nExample:\nx + y\n-786433 x1 + 655361 x2";
case "boxSubjTitle":
return "Subject";
case "boxSubjDesc":
return "Insert your subject here. One per line (divide by 'return' button). Allowed symbols are 0-9, a-z, A-Z and <>=.\nExample:\n+1 x + 2 y <= 15\n524321 x14 + 524305 x15 <= 4194303.5";
case "boxBoundsTitle":
return "Bounds";
case "boxBoundsDesc":
return "Insert your bounds here. One per line (divide by 'return' button). Allowed symbols are 0-9, a-z, A-Z and <>=.\nExample:\nx >= 0\nx > 0\n0 <= x1 <= 1";
case "boxVarsTitle":
return "Variables";
case "boxVarsDesc":
return "List all your variables. One per line (divide by 'return' button). Allowed symbols are a-z, A-Z.\nExample:\nx\ny";
case "boxExportLP":
return "Export as LP";
case "boxExportMPS":
return "Export as MPS";
case "boxOut":
return "Input a problem and an action button to display output...";
case "buttonCalc":
return "Calculate";
case "etime":
return "Elapsed time";
case "seconds":
return "seconds";
case "err_invalidInput":
return "Error: Invalid input in";
case "err_nullInput":
return "Error: NULL or undefined input in";
case "err_invalidInput":
return "Error: Invalid input or missing character in";
case "input_checks_successful":
return "All input checks successful."
case "input_checks_start":
return "Starting input checks...";
case "obj_box":
return "object box";
case "subj_box":
return "subject box";
case "bounds_box":
return "bounds box";
case "vars_box":
return "variables box";
case "err_emptyBox":
return "Error: Empty text box.";
case "calculating":
return "Calculating...";
case "maximize":
return "Maximize";
case "minimize":
return "Minimize";
case "run_optimization":
return "Running optimization with input";
case "startProblemSetup":
return "Starting problem setup...";
case "succProblemSetup":
return "Problem created.";
case "startScaling":
return "Scaling problem...";
case "succScaling":
return "Scaling successful.";
case "startOptimizationSimplex":
return "Starting simplex optimization...";
case "succOptimizationSimplex":
return "Simplex optimization complete.";
case "startOptimizationInteger":
return "Starting integer optimization...";
case "succOptimizationInteger":
return "Integer optimization complete.";
case "finalObjValue":
return "Final objective value";
case "varsValues":
return "Value of each variable";
case "dualValues":
return "Dual values of constraints";
case "downloadPrepFileString":
return "Preparing file content string...";
case "downloadPrepFile":
return "Preparing file...";
case "downloadStart":
return "Starting download.";
case "downloadPrep":
return "Preparing download...";
case "downloadFetchInput":
return "Fetching input...";
case "downloadCheckInput":
return "Checking for empty input boxes...";
case "importing":
return "Importing...";
case "err_invalidConstraintFormat":
return "Error: Invalid constraint format.";
default:
return input;
}
}
return "Error: Translation Module - Language Not Known.";
}
+3 -3
View File
@@ -45,7 +45,7 @@ export default function RootLayout({
width={16}
height={16}
/>
Go to our docs
Docs
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
@@ -60,7 +60,7 @@ export default function RootLayout({
width={16}
height={16}
/>
See the source code
Source Code
</a>
<a
className="flex items-center gap-2 hover:underline hover:underline-offset-4"
@@ -75,7 +75,7 @@ export default function RootLayout({
width={16}
height={16}
/>
Powered by GLPK
GLPK
</a>
</footer>
</div>
+72 -30
View File
@@ -1,58 +1,100 @@
'use client'
import React, { useState } from 'react';
import { Box, Button, Output } from "./modules";
import { calculate_click, downloadLP, import_click } from "./scripts"
import { calculate_click, downloadLP, downloadMPS } from "./scripts";
import text from "./lang";
export default function Home() {
const [language, setLanguage] = useState('eng');
const [maxminOption, setMaxminOption] = useState('maximize');
const tr_hTitle = text(language, 'header_title');
const tr_hSubtitle = text(language, 'header_subtitle');
const tr_boxObjTitle = text(language, 'boxObjTitle');
const tr_boxObjDesc = text(language, "boxObjDesc");
const tr_boxSubjTitle = text(language, 'boxSubjTitle');
const tr_boxSubjDesc = text(language, "boxSubjDesc");
const tr_boxBoundsTitle = text(language, 'boxBoundsTitle');
const tr_boxBoundsDesc = text(language, "boxBoundsDesc");
const tr_boxVarsTitle = text(language, 'boxVarsTitle');
const tr_boxVarsDesc = text(language, "boxVarsDesc");
const tr_boxOut = text(language, "boxOut");
const tr_boxExportLP = text(language, "boxExportLP");
const tr_calc_max = text(language, "maximize");
const tr_calc_min = text(language, "minimize");
const tr_calcButton = text(language, "buttonCalc");
const tr_boxExportMPS = text(language, "boxExportMPS");
const handleLanguageChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setLanguage(event.target.value);
};
const handleMaxMinChange = (event: React.ChangeEvent<HTMLSelectElement>) => {
setMaxminOption(event.target.value);
};
return (
<>
<header className="header">
<div className="title">
<main className="header_box">
Operations Research Tool
<br></br>
{tr_hTitle}
<br />
<span className="header_copyright">
<i>by Spaceholder Programming</i>
<i>{tr_hSubtitle}</i>
</span>
<br />
<select id="language_current" value={language} onChange={handleLanguageChange} className="dropdown-custom">
<option value="ger">Deutsch</option>
<option value="eng">English</option>
</select>
</main>
</div>
</header>
<Box
title={"Objective"}
placeholder={"Insert your objective here. One objective is allowed. Use one line for it (no \"return\"!) Allowed symbols are 0-9, a-z, A-Z and <>=.\nExample:\nx + y\n-786433 x1 + 655361 x2"}
id="objective"/>
title={tr_boxObjTitle}
placeholder={tr_boxObjDesc}
id="objective"
/>
<Box
title={"Subject"}
placeholder={"Insert your subject here. One per line (divide by 'return' button). Allowed symbols are 0-9, a-z, A-Z and <>=.\nExample:\n+1 x + 2 y <= 15\n524321 x14 + 524305 x15 <= 4194303.5"}
id="subject"/>
title={tr_boxSubjTitle}
placeholder={tr_boxSubjDesc}
id="subject"
/>
<Box
title={"Bounds"}
placeholder={"Insert your bounds here. One per line (divide by 'return' button). Allowed symbols are 0-9, a-z, A-Z and <>=.\nExample:\nx >= 0\nx > 0\n0 <= x1 <= 1"}
id="bounds"/>
title={tr_boxBoundsTitle}
placeholder={tr_boxBoundsDesc}
id="bounds"
/>
<Box
title={"Variables"}
placeholder={"List all your variables. One per line (divide by 'return' button). Allowed symbols are a-z, A-Z.\nExample:\nx\ny"}
id="vars" />
title={tr_boxVarsTitle}
placeholder={tr_boxVarsDesc}
id="vars"
/>
<select id="maxminswitch" value={maxminOption} onChange={handleMaxMinChange} className="dropdown-custom-maxmin">
<option value="maximize">{tr_calc_max}</option>
<option value="minimize">{tr_calc_min}</option>
</select>
<Button
title={"Calculate"}
title={tr_calcButton}
className={"button_green"}
onClickFunc={calculate_click} />
{/* <Popup_Button
title={"Import"}
className={"button"} /> */}
onClickFunc={calculate_click}
/>
<Button
title={"Export as LP"}
title={tr_boxExportLP}
className={"button"}
onClickFunc={downloadLP} />
<br></br>
onClickFunc={downloadLP}
/>
<Button
title={tr_boxExportMPS}
className={"button"}
onClickFunc={downloadMPS} />
<br />
<Output
id="out"
text={"Input a problem and an action button to display output..."}/>
{/* <Popup_Button
title="Popup"
className="button"
/> */}
text={tr_boxOut}
/>
</>
);
}
+465 -136
View File
@@ -1,30 +1,45 @@
import * as MIP from "../parser/parseMIP"
import * as LP from "../parser/parseLP"
import * as LPAPI from "../api/optimizeLP.js"
import { LP } from "../solver/jvail/LP";
import { Bound } from "../solver/jvail/Bound";
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 { start } from "repl";
import text from "./lang"
// custom log so we can append the output dynamically
function customLog(message: string) {
console.log(message); // Continue to print message inside of box
export function customLog(input: string) {
// get language
const lang = (document.getElementById('language_current') as HTMLSelectElement)?.value;
// Get Output Box
const outputElement = document.getElementById('out');
// load text
const message: string = text(lang, input);
console.log(message); // Continue to print message inside of box
// Append message if element exists
if (outputElement) {
outputElement.innerHTML += message + "<br>"; // Append message
}
}
function customLogClear() {
export function customLogClear() {
const outElement = document.getElementById('out');
if (outElement) {
outElement.innerHTML = "";
}
}
export function getTranslation(input: string) {
// get language
const lang = (document.getElementById('language_current') as HTMLSelectElement)?.value;
// return translation
return text(lang, input);
}
function walltimeStopAndPrint(startpoint: number) {
// calculating elapsed time as timestamp
let duration = Date.now() - startpoint;
@@ -37,7 +52,8 @@ function walltimeStopAndPrint(startpoint: number) {
const durationFormatted = seconds + (milliseconds >= 0 ? "." : ".") + Math.abs(milliseconds).toFixed(3).slice(2);
// Printing elapsed time
customLog("Elapsed time: " + durationFormatted + " seconds<br>");
customLog(getTranslation("etime") + ": " + durationFormatted + " " + getTranslation("seconds"));
customLog("");
// return durationFormatted;
}
@@ -46,12 +62,13 @@ function walltimeStart() {
return Date.now();
}
function isInputValidRegex(obj: string | undefined, subj: string | undefined, bounds: string | undefined, vars: string | undefined): boolean {
customLog("Staring input checks...");
export function isInputValidRegex(obj: string | undefined, subj: string | undefined, bounds: string | undefined, vars: string | undefined): boolean {
customLog("input_checks_start");
customLog("");
// standard case: input is undefined - invalid
if (obj === undefined || obj === null || subj === undefined || subj === null || bounds === undefined || bounds === null || vars === undefined || vars === null) {
customLog("Error: Function isInputValidRegex received undefined or null input.");
customLog(getTranslation("err_nullInput") + "function isInputValidRegex.");
return false;
}
@@ -59,7 +76,7 @@ function isInputValidRegex(obj: string | undefined, subj: string | undefined, bo
let regex = /^[ (\n)]*[\+-]? *((\d+(.\d+)? )?[a-zA-Z][a-zA-Z0-9]*)( *[\+-] *((\d+(.\d+)? )?[a-zA-Z][a-zA-Z0-9]*))*[ (\n)]*$/g;
let isValid = regex.test(obj);
if (!isValid) {
customLog("Error: Invalid or missing character in object box.");
customLog(getTranslation("err_invalidInput") + " " + getTranslation("obj_box") + ".");
return false;
}
@@ -67,46 +84,70 @@ function isInputValidRegex(obj: string | undefined, subj: string | undefined, bo
regex = /^([ (\n)]*[\+-]* *(\d+(.\d+)? )?[a-zA-Z][a-zA-Z0-9]*( *[\+-] *(\d+(.\d+)? )?[a-zA-Z][a-zA-Z0-9]*)* *((<=?)|(>=?)|=) *[\+-]? *\d+(.\d+)?[ (\n)]*)+$/g;
isValid = regex.test(subj);
if (!isValid) {
customLog("Error: Invalid or missing character in subject box.");
customLog(getTranslation("err_invalidInput") + " " + getTranslation("subj_box") + ".");
return false;
}
// RegEx check for subject
regex = /[ (\n)]*(([a-zA-Z][a-zA-Z0-9]* *((<=?)|(>=?)|=) *\d(.\d+)?)|((\d(.\d+)?) *<=? *[a-zA-Z][a-zA-Z0-9]* *<= *(\d(.\d+)?)))[ (\n)]*/g;
isValid = regex.test(bounds);
if (!isValid) {
customLog("Error: Invalid or missing character in bounds box.");
// RegEx check for subject
regex = /[ (\n)]*(([a-zA-Z][a-zA-Z0-9]* *((<=?)|(>=?)|=) *\d(.\d+)?)|((\d(.\d+)?) *<=? *[a-zA-Z][a-zA-Z0-9]* *<= *(\d(.\d+)?)))[ (\n)]*/g;
isValid = regex.test(bounds);
if (!isValid) {
customLog(getTranslation("err_invalidInput") + " " + getTranslation("bounds_box") + ".");
return false;
}
}
// RegEx check for variables
regex = /^ *([a-zA-Z][a-zA-Z0-9]*(\n)* *)+$/g;
isValid = regex.test(vars);
if (!isValid) {
customLog("Error: Invalid or missing character in variables box.");
customLog(getTranslation("err_invalidInput") + " " + getTranslation("vars_box") + ".");
return false;
}
customLog("All input checks successful.");
customLog("input_checks_successful");
customLog("");
return true;
}
function isInputFilled(obj: string | undefined, subj: string | undefined, bounds: string | undefined, vars: string | undefined) {
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) {
customLog("Error: Empty input field.");
customLog("err_emptyBox");
return false;
}
if (subj == "" || subj == null || subj == undefined) {
customLog("Error: Empty input field.");
customLog("err_emptyBox");
return false;
}
if (bounds == "" || bounds == null || bounds == undefined) {
customLog("Error: Empty input field.");
customLog("err_emptyBox");
return false;
}
if (vars == "" || vars == null || vars == undefined) {
customLog("Error: Empty input field.");
customLog("err_emptyBox");
return false;
}
return true;
@@ -115,7 +156,8 @@ function isInputFilled(obj: string | undefined, subj: string | undefined, bounds
export function calculate_click() {
customLogClear();
const timer = walltimeStart();
customLog("Calculating...<br>");
customLog("calculating");
customLog("");
let objective: string | undefined;
const objectiveElement = document.getElementById('objective');
@@ -141,105 +183,64 @@ export function calculate_click() {
variables = (varsElement as HTMLInputElement).value;
}
// let funcs:string[] = functions.split(/;/);
// let vars:string[] = variables.split(/;/);
// let direction = null;
// let namesVars:string[] = [];
// let variablesMIP:VariableMIP[];
// let variablesLP:VariableLP[];
// // console.log(vars);
// for (const decider of vars) {
// // match comments
// let regexMatch:RegExpMatchArray|null = decider.match(/#.*/);
// if (regexMatch != null)
// continue;
// regexMatch = decider.match(/var/);
// if (regexMatch != null)
// namesVars.push(regexMatch[1]);
// console.log(regexMatch);
// }
// for (const decider of funcs) {
// let dir = decider.match(/(min|max) .*/);
// if (direction != null && dir != null) {
// document.getElementById('out').innerHTML = "ERROR: Multiple Functions!";
// return;
// }
// if (direction == null && dir != null) {
// direction = dir[1];
// let test = parseFunction(decider);
// console.log(test?.name);
// variablesLP.
// continue;
// }
// console.log(direction);
// document.getElementById('out').innerHTML = direction;
// console.log(parseFunction(decider));
// catch error: empty input field(s)
if (!isInputFilled(objective, subject, bounds, variables)) return;
// catch error: variables field has invalid characters
if (!isInputValidRegex(objective, subject, bounds, variables)) return;
let wholeText: string = "Maximize\n obj: " + objective
// fetch operator
const maxmin = (document.getElementById('maxminswitch') as HTMLSelectElement)?.value;
let operator = "Minimize";
if (maxmin == "maximize") operator = "Maximize";
let wholeText: string = operator + "\n obj: " + objective
+ "\nSubject To \n" + subject
+ "\nBounds \n" + bounds
+ "\nGenerals \n" + variables
+ "\nEnd";
// customLog("<br><br>DEBUGGING<br><br>\nfunctions:<br>" + functions + "<br><br>variables:<br>" + variables + "<br><br>DEBUGGING END<br>");
customLog("Running optimization with input: \"" + wholeText + "\"<br>");
customLog(getTranslation("run_optimization") + ": \"" + wholeText + "\"");
customLog("");
run(wholeText);
walltimeStopAndPrint(timer);
}
function run(text: string) {
customLog("Starting problem setup...");
customLog("startProblemSetup");
let lp = GLPKAPI.glp_create_prob();
GLPKAPI.glp_read_lp_from_string(lp, null, text);
customLog("Problem created.<br>");
customLog("succProblemSetup");
customLog("");
customLog("Scaling problem...");
customLog("startScaling");
GLPKAPI.glp_scale_prob(lp, GLPKAPI.GLP_SF_AUTO);
customLog("Scaling complete.<br>");
customLog("succScaling");
customLog("");
customLog("Starting simplex optimization...");
customLog("startOptimizationSimplex");
let smcp = new GLPKAPI.SMCP({ presolve: GLPKAPI.GLP_ON });
GLPKAPI.glp_simplex(lp, smcp);
customLog("Simplex optimization complete.<br>");
customLog("succOptimizationSimplex");
customLog("");
customLog("Starting integer optimization...");
customLog("startOptimizationInteger");
let iocp = new GLPKAPI.IOCP({ presolve: GLPKAPI.GLP_ON });
GLPKAPI.glp_intopt(lp, iocp);
customLog("Integer optimization complete.<br>");
customLog("succOptimizationInteger");
customLog("");
// customLog("obj: " + GLPKAPI.glp_mip_obj_val(lp));
customLog("<i>Final objective value: " + GLPKAPI.glp_mip_obj_val(lp) + "</i><br>");
customLog("Value of each variable:");
for (let i = 1; i <= GLPKAPI.glp_get_num_cols(lp) - 1; i++) { // "-1" to remove the "End-variable" from logs
customLog("<i>" + getTranslation("finalObjValue") + ": " + GLPKAPI.glp_mip_obj_val(lp) + "</i>");
customLog("");
customLog(getTranslation("varsValues") + ":");
for (let i = 1; i <= GLPKAPI.glp_get_num_cols(lp); i++) {
customLog(GLPKAPI.glp_get_col_name(lp, i) + " = " + GLPKAPI.glp_mip_col_val(lp, i));
}
customLog("");
customLog("Dual values of constraints:");
customLog(getTranslation("dualValues") + ":");
for (let j = 1; j <= GLPKAPI.glp_get_num_rows(lp); j++) {
const dualValue = GLPKAPI.glp_get_row_dual(lp, j); // fetch dual
const constraintName = GLPKAPI.glp_get_row_name(lp, j);
@@ -248,19 +249,25 @@ function run(text: string) {
customLog("");
}
function downloadLPFormatting(objective: any, subject: any, bounds: any) {
customLog("Preparing file content string...<br>");
export function downloadLPFormatting(objective: any, subject: any, bounds: any) {
customLog(getTranslation("downloadPrepFileString"));
customLog("");
// ensure that all vars are strings
const formattedObjective = typeof objective === 'string' ? objective : '';
const formattedSubject = typeof subject === 'string' ? subject : '';
const formattedBounds = typeof bounds === 'string' ? bounds : '';
// Header mit Problemname
// fetch operator
const maxmin = (document.getElementById('maxminswitch') as HTMLSelectElement)?.value;
let operator = "Minimize";
if (maxmin == "maximize") operator = "Maximize";
// Header with problem name
const header = "\\ Your problem\n";
// format objective
const objectiveFunction = `Maximize\n obj: ${formattedObjective}\n`;
const objectiveFunction = operator + `\n obj: ${formattedObjective}\n`;
// turn each subject into a single line
const constraints = `Subject To\n${formattedSubject.split("\n").filter(line => line.trim() !== "").map(line => ` ${line}`).join("\n")}\n`;
@@ -274,24 +281,34 @@ function downloadLPFormatting(objective: any, subject: any, bounds: any) {
return lpFormat;
}
function downloadProblemDownload(content: string) {
customLog("Preparing file...<br>")
customLog("downloadPrepFile");
customLog("");
const blob = new Blob([content], { type: 'text/plain' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = 'problem.txt'; // file name
link.click(); // starting download
customLog("Starting download.")
customLog("downloadStart");
}
export function downloadLP() {
customLogClear();
customLog("Preparing download...<br>");
customLog("Fetching input...<br>")
customLog("downloadPrep");
customLog("");
customLog("downloadCheckInput");
customLog("");
if (!isInputFilled("","","","")) return;
let exportString: string | undefined = getInputsForLPAsString();
if (exportString === undefined) return;
downloadProblemDownload(exportString);
}
function getInputsForLP() {
let objective: string | undefined;
const objectiveElement = document.getElementById('objective');
if (objectiveElement !== null) {
@@ -316,48 +333,360 @@ export function downloadLP() {
variables = (varsElement as HTMLInputElement).value;
}
// catch error: empty input field(s)
if (!isInputFilled(objective, subject, bounds, variables)) return;
const exportString: string = downloadLPFormatting(objective, subject, bounds);
downloadProblemDownload(exportString);
return { objective, subject, bounds };
}
// Irgend ein Interface
// document.getElementById('out').innerHTML = funcs;
function getInputsForLPAsString(): string {
// output.innerHTML = functions.innerHTML;
let inputs = getInputsForLP();
let obj = inputs?.objective;
let sub = inputs?.subject;
let bnds = inputs?.bounds;
// createProblemMIP();
const exportString: string = downloadLPFormatting(obj, sub, bnds);
// LPAPI.default();
export function import_click() {
console.log("Importing...");
return exportString;
}
export function convertLPToMPS(lp: LP): string {
let mpsString = '';
// export function export_click() {
// console.log("Exporting...");
// 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`;
}
});
// function parseFunction(toParse: string) {
// var regex = toParse.match(/([a-zA-Z][a-zA-Z0-9]*):/);
// if (regex == null)
// return;
// var name = regex[1];
// 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 });
});
});
// regex = toParse.match(/(?:([0-9]*) *\* *([a-zA-Z][a-zA-Z0-9]*))/g);
for (const [variable, rows] of Object.entries(variableMap)) {
rows.forEach(entry => {
mpsString += ` ${variable} ${entry.row} ${entry.coef}\n`;
});
}
// let coefs:number[] = [];
// let vars:string[] = [];
// 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`;
}
});
// for (const rg of regex) {
// coefs.push(+rg.match(/([0-9]+)/g));
// vars.push(rg.match(/([a-zA-Z][a-zA-Z0-9]*)/g)[0]);
// }
// return {name, coefs, vars};
// }
// 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);
}
+6
View File
@@ -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;
}
+9
View File
@@ -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
+8
View File
@@ -0,0 +1,8 @@
import {Variable} from "./Variable";
import {Bounds} from "./Bounds";
export interface Constraint {
name: string;
vars: Variable[];
bounds: Bounds;
}
+18
View File
@@ -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
}
+12
View File
@@ -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
}
}
+10
View File
@@ -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 */
};
}
+4
View File
@@ -0,0 +1,4 @@
export interface Variable {
name: string;
coef: number
}