From 156197612a664de77d592c40a404387c8d0bc6c9 Mon Sep 17 00:00:00 2001 From: moebiusl Date: Fri, 11 Oct 2024 20:51:21 +0200 Subject: [PATCH 1/4] addPage for gmpl --- src/app/context/LanguageContext.tsx | 21 +++++++++ src/app/glp/page.tsx | 69 +++++++++++++++++++++++++++++ src/app/layout.tsx | 6 +++ src/app/page.tsx | 22 ++++++++- 4 files changed, 116 insertions(+), 2 deletions(-) create mode 100644 src/app/context/LanguageContext.tsx create mode 100644 src/app/glp/page.tsx diff --git a/src/app/context/LanguageContext.tsx b/src/app/context/LanguageContext.tsx new file mode 100644 index 0000000..96e9a7c --- /dev/null +++ b/src/app/context/LanguageContext.tsx @@ -0,0 +1,21 @@ +'use client'; + +import React, { createContext, useState, ReactNode } from 'react'; + +export const LanguageContext = createContext<{ + language: string; + setLanguage: (lang: string) => void; +}>({ + language: 'eng', + setLanguage: () => {}, +}); + +export const LanguageProvider = ({ children }: { children: ReactNode }) => { + const [language, setLanguage] = useState('eng'); + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/src/app/glp/page.tsx b/src/app/glp/page.tsx new file mode 100644 index 0000000..f965cf5 --- /dev/null +++ b/src/app/glp/page.tsx @@ -0,0 +1,69 @@ +'use client'; + +import { useRouter } from 'next/navigation'; +import React, { useState, useContext } from 'react'; +import text from "../lang"; +import { LanguageContext } from '../context/LanguageContext'; + + +const GlpPage = () => { + const router = useRouter(); + + const { language, setLanguage } = useContext(LanguageContext); + const [model, setModel] = useState('gen'); + + 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) => { + setLanguage(event.target.value); + }; + + const changeModel = (event: React.ChangeEvent) => { + const selectedModel = event.target.value; + setModel(selectedModel); + + if (selectedModel === 'spec') { + router.push('/'); + } + }; + + return ( +
+
+
+ {tr_hTitle} +
+ + {tr_hSubtitle} + +
+ + +
+
+
+ ); +}; + +export default GlpPage; \ No newline at end of file diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 6f3f03b..0542883 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,6 +1,7 @@ import type { Metadata } from "next"; import Image from "next/image"; import localFont from "next/font/local"; +import { LanguageProvider } from './context/LanguageContext'; // Importiere den Provider import "./globals.css"; const geistSans = localFont({ @@ -19,16 +20,21 @@ export const metadata: Metadata = { description: "OR-Tool by Spaceholder Programming", }; + export default function RootLayout({ children, }: Readonly<{ children: React.ReactNode; }>) { return ( + + + {children} {} + {children}
diff --git a/src/app/page.tsx b/src/app/page.tsx index b321e2e..c4c0260 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,13 +1,19 @@ 'use client' -import React, { useState } from 'react'; +import React, { useState, useContext } from 'react'; import { Box, Button, Output } from "./modules"; import { calculate_click, downloadLP, downloadMPS } from "./scripts"; import text from "./lang"; +import { spec } from 'node:test/reporters'; +import { useRouter } from 'next/navigation'; +import { LanguageContext } from './context/LanguageContext'; export default function Home() { - const [language, setLanguage] = useState('eng'); + const { language, setLanguage } = useContext(LanguageContext); const [maxminOption, setMaxminOption] = useState('maximize'); + const [model] = useState('spec'); + const router = useRouter(); + const tr_hTitle = text(language, 'header_title'); const tr_hSubtitle = text(language, 'header_subtitle'); @@ -30,6 +36,14 @@ export default function Home() { setLanguage(event.target.value); }; + const changeModel = (event: React.ChangeEvent) => { + const selectedModel = event.target.value; + + if (selectedModel === 'gen') { + router.push('./glp'); + } + }; + const handleMaxMinChange = (event: React.ChangeEvent) => { setMaxminOption(event.target.value); }; @@ -49,6 +63,10 @@ export default function Home() { +
-- 2.52.0 From 255069aaf39643fdf9c1439a02658fb8d549a8eb Mon Sep 17 00:00:00 2001 From: moebiusl Date: Fri, 11 Oct 2024 21:43:40 +0200 Subject: [PATCH 2/4] implement gmpl full_functional --- src/app/globals.css | 168 ++++++++++++++++++ src/app/glp/page.tsx | 403 +++++++++++++++++++++++++++++++++++++------ src/app/lang.ts | 24 ++- src/app/page.tsx | 6 +- 4 files changed, 545 insertions(+), 56 deletions(-) diff --git a/src/app/globals.css b/src/app/globals.css index 790cffc..faf52dd 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -61,6 +61,14 @@ body { padding: 10px; } +.button_spec { + border: 2px solid #5353535c; + background-color: #1010105c; + border-radius: 20px; + margin: 10px; + padding: 20px; +} + .button:hover { border: 2px solid #5353535c; background-color: #5353535c; @@ -186,3 +194,163 @@ body { font-size: 16px; cursor: pointer; } + +.containerGmpl { + display: flex; + flex-direction: column; + margin: 20px; + padding: 20px; + background-color: #2a2a2a; + border-radius: 10px; + border: 1px solid #8d8d8d; + max-width: 90%; + margin-left: auto; + margin-right: auto; +} + +.headerGmpl { + font-size: 24px; + color: #ffffff; + text-align: center; + margin-bottom: 20px; +} + +.fileInput { + margin-bottom: 20px; +} + +.buttoncontainerGmpl { + display: flex; + justify-content: flex-end; +} + +.buttonGmpl { + padding: 10px 20px; + font-size: 16px; + background-color: #28a745; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.togglebuttonGmpl { + margin: 10px 0; + padding: 10px; + background-color: #007bff; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.fileContentBox { + background-color: #3c3c3c; + color: #ffffff; + padding: 15px; + margin-bottom: 20px; + border-radius: 5px; + font-family: 'Courier New', Courier, monospace; + white-space: pre-line; +} + +.textarea { + width: 100%; + background-color: #202020; + color: #ffffff; + border: 1px solid #8d8d8d; + border-radius: 5px; + padding: 10px; + font-family: 'Courier New', Courier, monospace; + font-size: 16px; + margin-bottom: 10px; + overflow: scroll; + max-height: 800px; + min-height: 400px; +} + +.downloadbuttonGmpl { + padding: 10px 20px; + font-size: 16px; + background-color: #28a745; + color: white; + border: none; + border-radius: 4px; + cursor: pointer; +} + +.loadingSpinner { + color: #fff; + font-size: 16px; + text-align: center; + margin-top: 20px; +} + +.pre { + margin: 0; +} + +.subheaderGmpl { + color: #ffffff; + margin-bottom: 10px; +} + +.msgZone { + margin-top: 20px; + color: #ffcc00; + background-color: #333; + padding: 10px; + border-radius: 5px; +} + +.syntaxErrorBox { + margin-top: 20px; + padding: 10px; + background-color: #ffcc00; + border-radius: 5px; + color: #333; +} + +.errorPopup { + position: fixed; + bottom: 20px; + left: 20px; + padding: 10px; + background-color: #ffcc00; + color: #333; + border-radius: 5px; + box-shadow: 0px 0px 10px rgba(0, 0, 0, 0.2); +} + +.popup { + position: fixed; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.7); + display: flex; + justify-content: center; + align-items: center; +} + +.popupContent { + background-color: #333; + padding: 20px; + border-radius: 10px; + color: #fff; + text-align: center; + width: 80%; + max-width: 600px; +} + +.popupResult { + max-height: 300px; + overflow-y: auto; + text-align: left; + padding: 10px; + border: 1px solid #8d8d8d; + background-color: #202020; + color: #ffffff; + border-radius: 5px; +} \ No newline at end of file diff --git a/src/app/glp/page.tsx b/src/app/glp/page.tsx index f965cf5..ff0e903 100644 --- a/src/app/glp/page.tsx +++ b/src/app/glp/page.tsx @@ -1,69 +1,368 @@ 'use client'; -import { useRouter } from 'next/navigation'; -import React, { useState, useContext } from 'react'; +import { useRouter } from 'next/navigation'; +import React, { useState, useContext, useRef, useEffect } from 'react'; import text from "../lang"; import { LanguageContext } from '../context/LanguageContext'; +import * as GLPKAPI from "../../solver/glpk.min.js" + const GlpPage = () => { - const router = useRouter(); + const router = useRouter(); - const { language, setLanguage } = useContext(LanguageContext); - const [model, setModel] = useState('gen'); + const { language, setLanguage } = useContext(LanguageContext); + const [model, setModel] = useState('gen'); - 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 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_calcButton = text(language, "buttonCalc"); + const tr_GmplTitle = text(language, 'GmplHeader'); + const tr_GenProblems = text(language, 'GenProblem'); + const tr_SpecProblems = text(language, 'SpecProblem'); + const tr_fileUpload = text(language, 'FileUpload'); + const tr_fileName = text(language, 'FileName'); - const handleLanguageChange = (event: React.ChangeEvent) => { - setLanguage(event.target.value); - }; - const changeModel = (event: React.ChangeEvent) => { - const selectedModel = event.target.value; - setModel(selectedModel); - if (selectedModel === 'spec') { - router.push('/'); - } - }; - return ( -
-
-
- {tr_hTitle} -
- - {tr_hSubtitle} - -
- - -
+ const [fileContent, setFileContent] = useState(''); + const [isFileUploaded, setIsFileUploaded] = useState(false); + const [showFileContent, setShowFileContent] = useState(true); + const [isLoading, setIsLoading] = useState(false); + const [solverTime, setSolverTime] = useState(''); + const [showPopup, setShowPopup] = useState(false); + const [resultContent, setResultContent] = useState(''); + const [syntaxErrors, setSyntaxErrors] = useState([]); + const [showErrorPopup, setShowErrorPopup] = useState(false); + const solverTimeoutRef = useRef(null); + const solverAbortController = useRef(null); + const [highlightedContent, setHighlightedContent] = useState(''); + const [fileName, setFileName] = useState(""); + + + const handleLanguageChange = (event: React.ChangeEvent) => { + setLanguage(event.target.value); + }; + + const changeModel = (event: React.ChangeEvent) => { + const selectedModel = event.target.value; + setModel(selectedModel); + + if (selectedModel === 'spec') { + router.push('/'); + } + }; + + useEffect(() => { + if (syntaxErrors.length > 0) { + setShowErrorPopup(true); + } + }, [syntaxErrors]); + + const addMessage = (message: string) => { + const msgZone = document.getElementById("msgZone"); + if (msgZone) { + msgZone.innerHTML += `
${message}
`; + } + }; + + const updateHighlightedContent = (content: string) => { + const highlighted = highlightErrors(content); + setHighlightedContent(highlighted); + }; + + + const handleFileUpload = (event: React.ChangeEvent) => { + const file = event.target.files?.[0]; + if (file) { + setFileName(file.name); + } + if (!file) return; + + const reader = new FileReader(); + reader.onload = (e) => { + const content = e.target?.result as string; + setFileContent(content); + updateHighlightedContent(content); + addMessage("File successfully uploaded and read."); + setIsFileUploaded(true); + + // Perform syntax check + const errors = checkSyntax(content); + setSyntaxErrors(errors); + updateHighlightedContent(content); + }; + reader.readAsText(file); + }; + + const checkSyntax = (content: string): string[] => { + const errors: string[] = []; + const lines = content.split("\n"); + + const ignorePattern = /^(#|\/\/|printf|\/\*|\*).*$/; + + lines.forEach((line, index) => { + if (ignorePattern.test(line) || line.trim() === "") { + return; // Ignore this line + } + + const validGmplLinePattern = /^(var|param|set|maximize|minimize|s\.t\.|subject to|for|in|if|then|else|end|:=|<=|>=|=|\+|\-|\*|\/|[a-zA-Z_][a-zA-Z0-9_]*\s*[=<>+\-*/]*\s*[0-9a-zA-Z_]+.*;)/; + + if (!validGmplLinePattern.test(line)) { + errors.push(`Syntax error on line ${index + 1}: ${line}`); + } + }); + + return errors; + }; + + // Highlight syntax errors in the file content + const highlightErrors = (content: string) => { + const lines = content.split("\n"); + return lines.map((line, index) => { + const error = syntaxErrors.find((error) => error.includes(`line ${index + 1}`)); + if (error) { + return `${line}`; + } + return line; + }).join("\n"); + }; + + // Function to dynamically set the height of the textarea + const getTextAreaHeight = (value: any) => { + const stringValue = String(value); + const lineCount = stringValue.split("\n").length; + if (lineCount <= 3) return lineCount; + return 3; + }; + + // Function to abort solving after a certain time + const abortSolving = () => { + if (solverAbortController.current) { + solverAbortController.current.abort(); + setIsLoading(false); + addMessage("Solving was aborted after the timeout."); + } + }; + + const solve = () => { + addMessage("Starting the solver..."); + setIsLoading(true); + const startTime = performance.now(); + + // Set a timeout to abort the solver after 5 seconds + solverTimeoutRef.current = setTimeout(abortSolving, 5000); + + solverAbortController.current = new AbortController(); + const { signal } = solverAbortController.current; + + try { + var model = fileContent; + var lp = GLPKAPI.glp_create_prob(); + var tran = GLPKAPI.glp_mpl_alloc_wksp(); + GLPKAPI._glp_mpl_init_rand(tran, 1); + + GLPKAPI.glp_mpl_read_model_from_string(tran, "model", model, 0); + GLPKAPI.glp_mpl_generate(tran, null, function (data) { }); + addMessage("Model successfully converted to solver format."); + + GLPKAPI.glp_mpl_build_prob(tran, lp); + var smcp = new GLPKAPI.SMCP({ presolve: GLPKAPI.GLP_ON }); + GLPKAPI.glp_simplex(lp, smcp); + + var iocp = new GLPKAPI.IOCP({ presolve: GLPKAPI.GLP_ON }); + GLPKAPI.glp_intopt(lp, iocp); + GLPKAPI.glp_mpl_postsolve(tran, lp, GLPKAPI.GLP_MIP); + + addMessage("Model solved successfully."); + setIsLoading(false); + + const endTime = performance.now(); + const solverDuration = ((endTime - startTime) / 1000).toFixed(2); + setSolverTime(`Solver time: ${solverDuration} seconds`); + addMessage(`Solver time: ${solverDuration} seconds`); + + var status; + switch (GLPKAPI.glp_mip_status(lp)) { + case GLPKAPI.GLP_OPT: + status = "OPTIMAL"; + break; + case GLPKAPI.GLP_UNDEF: + status = "UNDEFINED SOLUTION"; + break; + case GLPKAPI.GLP_INFEAS: + status = "INFEASIBLE SOLUTION"; + break; + case GLPKAPI.GLP_NOFEAS: + status = "NO FEASIBLE SOLUTION"; + break; + case GLPKAPI.GLP_FEAS: + status = "FEASIBLE SOLUTION"; + break; + case GLPKAPI.GLP_UNBND: + status = "UNBOUNDED SOLUTION"; + break; + } + + const result = `Solution status: ${status}`; + let variables = "Variable results:\n"; + for (var i = 1; i <= GLPKAPI.glp_get_num_cols(lp); i++) { + variables += `${GLPKAPI.glp_get_col_name(lp, i)} = ${GLPKAPI.glp_mip_col_val(lp, i)}\n`; + } + if (solverTimeoutRef.current) clearTimeout(solverTimeoutRef.current); + setResultContent(`${result}\n\n${variables}\n\nSolver time: ${solverDuration} seconds`); + setShowPopup(true); + + } catch (err) { + setIsLoading(false); + addMessage("
" + err.toString() + "
"); + console.log(err); + } + }; + + // Function to download the modified file + const downloadFile = () => { + const element = document.createElement("a"); + const file = new Blob([fileContent], { type: 'text/plain' }); + element.href = URL.createObjectURL(file); + element.download = "problem.gmpl"; + document.body.appendChild(element); + element.click(); + document.body.removeChild(element); + }; + + return ( +
+
+
+
+ {tr_hTitle} +
+ + {tr_hSubtitle} + +
+ + +
+
+
+ +
+

{tr_GmplTitle}

+ + {/* File Upload */} +
+ + + + {fileName ? fileName : tr_fileName} + +
+ + {isFileUploaded && ( + <> +
+ +
+ + )} + + {fileContent && ( +
+ {showFileContent && ( +
+

File Content

+ {/* Use a textarea for editable text */} +