diff options
Diffstat (limited to 'static')
| -rw-r--r-- | static/favicon.ico | bin | 0 -> 318 bytes | |||
| -rw-r--r-- | static/script.js | 240 | ||||
| -rw-r--r-- | static/styles.css | 5 | 
3 files changed, 245 insertions, 0 deletions
| diff --git a/static/favicon.ico b/static/favicon.icoBinary files differ new file mode 100644 index 0000000..4e0f88c --- /dev/null +++ b/static/favicon.ico diff --git a/static/script.js b/static/script.js new file mode 100644 index 0000000..d8dbf77 --- /dev/null +++ b/static/script.js @@ -0,0 +1,240 @@ +"use strict"; + +var ingredientsm = {} +var allIngredients = [] +for (let recipe of allRecipes) { +    for (let ingredient of recipe.ingredients) { +        if (ingredient.special) { +            continue +        } +        if (!ingredientsm[ingredient.ingredient]) { +            allIngredients.push(ingredient.ingredient) +            ingredientsm[ingredient.ingredient] = 1 +        } +    } +} +allIngredients.sort() +var ingredientsLookup = {} +for (let [idx, ingredient] of allIngredients.entries()) { +    ingredientsLookup[ingredient] = idx +} + +var globret = undefined; + +window.addEventListener('load', function(evt) { +    glpkPromise.then(function(glpk) { + +        const worker = new Worker('/static/glpk-worker.js') +        worker.onerror = (err) => { console.log(err) } +        worker.onmessage = (evt) => { +            if (evt.data.initialized) {} +            if (evt.data.result) { +                hide(document.getElementById('waiting')) +                show(document.getElementById('solution')) +                renderSolution(glpk, evt.data, globret) +            } +        } + +        document.getElementById('optimal').addEventListener('click', function(evt) { +            evt.preventDefault() +            window.globret = constructILP(glpk) +            solveILP(worker, globret) +        }) +    }) +}) + + +function renderSolution(glpk, sol, ret) { +    let ingredients = ret.ingredients +    let recipes = JSON.parse(JSON.stringify(ret.recipes)) +    let res = [] +    let batches = [] +    let origbatch = [] +    for (let [r, recipe] of recipes.entries()) { +        if (recipe.length == 0) { +            origbatch.push(r) +        } +    } +    for (let [varname, on] of Object.entries(sol.result.vars)) { +        if (!on) { continue } +        if (!varname.startsWith('y')) { continue } +        let y = Number.parseInt(varname.split('_')[1]) +        if (res.indexOf(y) != -1) { continue } +        res.push(y) +        let batch = [] +        for (let [r, recipe] of recipes.entries()) { +            if (recipe.length == 1 & recipe[0] == y) { +                batch.push(r) +            } +        } +        for (let [r, recipe] of recipes.entries()) { +            recipes[r] = recipe.filter(it => it != y) +        } +        batches.push(batch) +    } +    let ol = document.createElement('ol') + +    let li = document.createElement('li') +    li.appendChild(document.createTextNode('On-hand')) +    if (origbatch.length) { +        let ul = document.createElement('ul') +        let drinks = origbatch.map(b => allRecipes[b].name).join(', ') +        let sli = document.createElement('li') +        sli.appendChild(document.createTextNode(drinks)) +        ul.appendChild(sli) +        li.appendChild(ul) +    } +    ol.appendChild(li) + +    for (let [idx, y] of res.entries()) { +        let batch = batches[idx] +        let li = document.createElement('li') +        li.appendChild(document.createTextNode(ingredients[y])) +        if (batch.length) { +            let ul = document.createElement('ul') +            let drinks = batch.map(b => allRecipes[b].name).join(', ') +            let sli = document.createElement('li') +            sli.appendChild(document.createTextNode(drinks)) +            ul.appendChild(sli) +            li.appendChild(ul) +        } +        ol.appendChild(li) +    } +    document.getElementById('solution').innerHTML = '' +    let desc = document.createElement('p') +    if (sol.result.status == glpk.GLP_UNDEF) { +        desc.appendChild(document.createTextNode('Error: Failed to find feasible solution. Try increasing time limit.')) +    } else if (sol.result.status == glpk.GLP_FEAS) { +        desc.appendChild(document.createTextNode(`Found feasible solution of score ${-sol.result.z}.`)) +    } else if (sol.result.status == glpk.GLP_INFEAS) { +        desc.appendChild(document.createTextNode('Error: Did not find feasible solution.')) +    } else if (sol.result.status == glpk.GLP_NOFEAS) { +        desc.appendChild(document.createTextNode('Error: Problem has no feasible solution.')) +    } else if (sol.result.status == glpk.GLP_OPT) { +        desc.appendChild(document.createTextNode(`Found optimal solution of score ${-sol.result.z}.`)) +    } else if (sol.result.status == glpk.GLP_UNBND) { +        desc.appendChild(document.createTextNode('Error: Problem is unbounded.')) +    } +    document.getElementById('solution').appendChild(desc) +    if (sol.result.status == glpk.GLP_FEAS || sol.result.status == glpk.GLP_OPT) { +        document.getElementById('solution').appendChild(ol) +    } +} + +let hide = e => e.style.display = 'none'; +let show = e => e.style.display = 'inline-block'; + +function solveILP(worker, ret) { +    let seconds = Number.parseInt(document.getElementById('limit').value) +    document.getElementById('waiting').innerHTML = seconds + ' seconds...' +    hide(document.getElementById('solution')) +    show(document.getElementById('waiting')) +    let incrSeconds = () => { +        document.getElementById('waiting').innerHTML = seconds + ' seconds...' +        seconds-- +    } +    setInterval(incrSeconds, 1000) +    worker.postMessage(ret.lp) +} + +function constructILP(glpk) { +    let skip = Object.values(document.querySelector('form')).reduce((obj, field) => { +        if (field.getAttribute('x-ingredient') && field.checked) { +            obj[field.getAttribute('x-ingredient')] = 1 +        } +        return obj +    }, {}) + +    let ingredientsset = {} +    let recipes = [] + +    for (let recipe of allRecipes) { +        for (let ingredient of recipe.ingredients) { +            if (ingredient.special) { continue } +            if (skip[ingredientsLookup[ingredient.ingredient]]) { continue } +            ingredientsset[ingredient.ingredient] = 1 +        } +    } +    let ingredients = Object.keys(ingredientsset) + +    for (let recipe of allRecipes) { +        let x = [] +        for (let ingredient of recipe.ingredients) { +            if (ingredient.special) { continue } +            if (skip[ingredientsLookup[ingredient.ingredient]]) { continue } +            x.push(ingredients.indexOf(ingredient.ingredient)) +        } +        recipes.push(x) +    } +    let n_ingredients = ingredients.length + +	let makeStepR = (step, r) => `r${step}_${r}` +	let makeStepY = (step, y) => `y${step}_${y}` + +    let jsonvars = [] +    for (let step = 0; step < n_ingredients; step++) { +        for (let [r, recipe] of recipes.entries()) { +            jsonvars.push({name: makeStepR(step, r), coef: -1}) +        } +    } +    let jsonobjective = { +        direction: glpk.GLP_MIN, +        vars: jsonvars, +    } +    let jsonconstraints = [] +    for (let step = 0; step < n_ingredients; step++) { +        let vars = [] +        for (let y = 0; y < n_ingredients; y++) { +            vars.push({name: makeStepY(step, y), coef: 1}) +        } +        jsonconstraints.push({vars, bnds: {type: glpk.GLP_FX, lb: step+1}}) +    } +    for (let y = 0; y < n_ingredients; y++) { +        for (let step = 0; step < n_ingredients - 1; step++) { +            jsonconstraints.push({ +                vars: [{name: makeStepY(step+1, y), coef: 1}, {name: makeStepY(step, y), coef: -1}], +                bnds: {type: glpk.GLP_LO, lb: 0} +            }) +        } +    } +    for (let [r, recipe] of recipes.entries()) { +        for (let step = 0; step < n_ingredients; step++) { +            let total = [] +            for (let y of recipe) { +                total.push({name: makeStepY(step, y), coef: -1}) +            } +            total.push({name: makeStepR(step, r), coef: 1}) +            jsonconstraints.push({ +                vars: total, +                bnds: {type: glpk.GLP_LO, lb: (-recipe.length + 1)} +            }) +            for (let y of recipe) { +                jsonconstraints.push({ +                    vars: [{name: makeStepR(step, r), coef: 1}, {name: makeStepY(step, y), coef: -1}], +                    bnds: {type: glpk.GLP_UP, ub: 0} +                }) +            } +        } +    } +    let binaries = [] +    for (let y = 0; y < n_ingredients; y++) { +        for (let step = 0; step < n_ingredients; step++) { +            binaries.push(makeStepY(step, y)) +        } +    } +    for (let r = 0; r < recipes.length; r++) { +        for (let step = 0; step < n_ingredients; step++) { +            binaries.push(makeStepR(step, r)) +        } +    } +    let lp = { +        objective: jsonobjective, +        subjectTo: jsonconstraints, +        binaries, +        options: { +            msglev: 3, +            tmlim: Number.parseInt(document.getElementById('limit').value), +        } +    } +    return {ingredients, lp, recipes} +} diff --git a/static/styles.css b/static/styles.css new file mode 100644 index 0000000..4442223 --- /dev/null +++ b/static/styles.css @@ -0,0 +1,5 @@ +.ingredient { +    display: inline-block; +    margin-right: 5px; +    margin-bottom: 5px; +} | 
