mirror of
https://github.com/aziis98/ro-vis.git
synced 2026-10-06 22:55:20 +00:00
feat: grid for a professional look and mobile support
This commit is contained in:
@@ -6,14 +6,18 @@ export const DisplayProblemInput = ({ problemInput }: { problemInput: ProblemInp
|
||||
const { A, b, c, B } = problemInput
|
||||
|
||||
return (
|
||||
<Katex
|
||||
formula={[
|
||||
'\\begin{cases} \\max c^t x \\\\ Ax \\leq b \\end{cases}',
|
||||
`A = ${matrixToLatex(A)}`,
|
||||
`b = ${vectorToLatex(b)}`,
|
||||
`c^t = ${rowVectorToLatex(c)}`,
|
||||
`B = \\{${B.map(r => (r + 1).toString()).join(', ')}\\}`,
|
||||
].join(' \\qquad ')}
|
||||
/>
|
||||
<div class="scrollable">
|
||||
<div class="scroll-content">
|
||||
<Katex
|
||||
formula={[
|
||||
'\\begin{cases} \\max c^t x \\\\ Ax \\leq b \\end{cases}',
|
||||
`A = ${matrixToLatex(A)}`,
|
||||
`b = ${vectorToLatex(b)}`,
|
||||
`c^t = ${rowVectorToLatex(c)}`,
|
||||
`B = \\{${B.map(r => (r + 1).toString()).join(', ')}\\}`,
|
||||
].join(' \\qquad ')}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
+53
-31
@@ -1,5 +1,6 @@
|
||||
import { MobileScrollable } from './components'
|
||||
import { Katex } from './Katex'
|
||||
import { fillDot, drawSemiplane, drawSimpleArrow } from './lib-v2/canvas'
|
||||
import { fillDot, drawSemiplane, drawSimpleArrow, strokeInfiniteLine } from './lib-v2/canvas'
|
||||
import { range } from './lib-v2/math'
|
||||
import { Matrix } from './lib-v2/matrix'
|
||||
import { Rational } from './lib-v2/rationals'
|
||||
@@ -47,7 +48,7 @@ const PrimalStep = ({
|
||||
return (
|
||||
<div class="step">
|
||||
<div class="algebraic-step">
|
||||
<div class="row">
|
||||
<div class="comment">
|
||||
<p>
|
||||
Iterazione <strong>{iter + 1}</strong> dell'algoritmo
|
||||
</p>
|
||||
@@ -55,11 +56,11 @@ const PrimalStep = ({
|
||||
|
||||
{comments.map(comment =>
|
||||
comment.type === 'formula' ? (
|
||||
<div class="row">
|
||||
<MobileScrollable>
|
||||
<Katex formula={comment.content} />
|
||||
</div>
|
||||
</MobileScrollable>
|
||||
) : (
|
||||
<div class="row">
|
||||
<div class="comment">
|
||||
<MiniMark content={comment.content} />
|
||||
</div>
|
||||
)
|
||||
@@ -94,6 +95,9 @@ export const Primale = ({ input }: { input: ProblemInput }) => {
|
||||
|
||||
return (
|
||||
<div class="steps">
|
||||
<div class="title">
|
||||
<h2>Svolgimento</h2>
|
||||
</div>
|
||||
{problemOutput.steps.map((step, iter) => (
|
||||
<PrimalStep
|
||||
{...{
|
||||
@@ -159,6 +163,9 @@ const PrimalCanvas = ({
|
||||
g.textAlign = 'center'
|
||||
g.textBaseline = 'middle'
|
||||
|
||||
const [c1, c2] = c.getData()
|
||||
const cLen = Math.sqrt(c1.toNumber() ** 2 + c2.toNumber() ** 2)
|
||||
|
||||
// // draw y axis arrow
|
||||
// g.beginPath()
|
||||
// g.moveTo(width / 2, height / 2)
|
||||
@@ -177,27 +184,6 @@ const PrimalCanvas = ({
|
||||
// g.lineTo(width - 15, height / 2 + 10)
|
||||
// g.stroke()
|
||||
|
||||
// draw c vector
|
||||
const [c1, c2] = c.getData()
|
||||
const cLen = Math.sqrt(c1.toNumber() ** 2 + c2.toNumber() ** 2)
|
||||
|
||||
// g.save()
|
||||
g.strokeStyle = 'darkgreen'
|
||||
g.lineWidth = 2
|
||||
drawSimpleArrow(
|
||||
g,
|
||||
50,
|
||||
height - 50,
|
||||
50 + (c1.toNumber() / cLen) * 40,
|
||||
height - 50 - (c2.toNumber() / cLen) * 40,
|
||||
5
|
||||
)
|
||||
|
||||
g.fillStyle = 'darkgreen'
|
||||
fillDot(g, 50, height - 50, 4)
|
||||
|
||||
g.fillText(`c`, 50 - (c1.toNumber() / cLen) * 20, height - 50 + (c2.toNumber() / cLen) * 20)
|
||||
|
||||
// g.beginPath()
|
||||
// g.translate(50, height - 50)
|
||||
// g.rotate(Math.atan2(c2.toNumber(), c1.toNumber()))
|
||||
@@ -220,6 +206,22 @@ const PrimalCanvas = ({
|
||||
g.scale(width / 2, -width / 2)
|
||||
g.scale(1 / 10, 1 / 10)
|
||||
|
||||
// draw grid
|
||||
|
||||
g.strokeStyle = '#ddd'
|
||||
g.lineWidth = 20 / g.canvas.width
|
||||
for (let i = -10; i <= 10; i++) {
|
||||
strokeInfiniteLine(g, i, 0, Math.PI / 2)
|
||||
}
|
||||
for (let i = -9; i <= 9; i++) {
|
||||
strokeInfiniteLine(g, 0, i, 0)
|
||||
}
|
||||
|
||||
g.strokeStyle = '#333'
|
||||
g.lineWidth = 40 / g.canvas.width
|
||||
drawSimpleArrow(g, 0, 0, 9.8, 0, 0.35, '#444')
|
||||
drawSimpleArrow(g, 0, 0, 0, 9.8, 0.35, '#444')
|
||||
|
||||
// draw semiplanes
|
||||
|
||||
// draw semiplanes not in B
|
||||
@@ -248,15 +250,15 @@ const PrimalCanvas = ({
|
||||
if (x) {
|
||||
const [x1, x2] = x.getData()
|
||||
|
||||
g.lineWidth = 30 / g.canvas.width
|
||||
g.strokeStyle = 'darkgreen'
|
||||
g.lineWidth = 50 / g.canvas.width
|
||||
drawSimpleArrow(
|
||||
g,
|
||||
x1.toNumber(),
|
||||
x2.toNumber(),
|
||||
x1.toNumber() + c1.toNumber() / cLen,
|
||||
x2.toNumber() + c2.toNumber() / cLen,
|
||||
0.125
|
||||
0.25,
|
||||
'darkgreen'
|
||||
)
|
||||
|
||||
// draw xi
|
||||
@@ -264,7 +266,6 @@ const PrimalCanvas = ({
|
||||
const [xi1, xi2] = xi.getData()
|
||||
const xiLen = Math.sqrt(xi1.toNumber() ** 2 + xi2.toNumber() ** 2)
|
||||
|
||||
g.strokeStyle = '#44d'
|
||||
g.lineWidth = 50 / g.canvas.width
|
||||
drawSimpleArrow(
|
||||
g,
|
||||
@@ -272,13 +273,34 @@ const PrimalCanvas = ({
|
||||
x2.toNumber(),
|
||||
x1.toNumber() + xi1.toNumber() / xiLen,
|
||||
x2.toNumber() + xi2.toNumber() / xiLen,
|
||||
0.125
|
||||
0.25,
|
||||
'#44d'
|
||||
)
|
||||
}
|
||||
|
||||
g.fillStyle = '#d44'
|
||||
fillDot(g, x1.toNumber(), x2.toNumber(), 0.2)
|
||||
}
|
||||
|
||||
g.resetTransform()
|
||||
|
||||
// draw c vector
|
||||
|
||||
g.lineWidth = 2
|
||||
drawSimpleArrow(
|
||||
g,
|
||||
50,
|
||||
height - 50,
|
||||
50 + (c1.toNumber() / cLen) * 40,
|
||||
height - 50 - (c2.toNumber() / cLen) * 40,
|
||||
7,
|
||||
'darkgreen'
|
||||
)
|
||||
|
||||
g.fillStyle = 'darkgreen'
|
||||
fillDot(g, 50, height - 50, 4)
|
||||
|
||||
g.fillText(`c`, 50 - (c1.toNumber() / cLen) * 20, height - 50 + (c2.toNumber() / cLen) * 20)
|
||||
}
|
||||
|
||||
return <canvas ref={render} />
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
import { JSX } from 'preact/jsx-runtime'
|
||||
|
||||
export const MobileScrollable = ({ children }: { children: JSX.Element }) => {
|
||||
return (
|
||||
<div class="scrollable">
|
||||
<div class="scroll-content">{children}</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
[
|
||||
{
|
||||
"name": "Pintel",
|
||||
"source": "c' = 500 200;\\n\\nA = 1 0\\n 0 1\\n 2 1\\n -1 0\\n 0 -1;\\n\\nb = 4\\n 7\\n 9\\n 0\\n 0;\\n\\nB = 2 3;"
|
||||
},
|
||||
{
|
||||
"name": "Pintel non amm",
|
||||
"source": "c' = 500 200;\\n\\nA = 1 0\\n 0 1\\n 2 1\\n -1 0\\n 0 -1;\\n\\nb = 4\\n 7\\n 9\\n 0\\n 0;\\n\\nB = 1 2;"
|
||||
},
|
||||
{
|
||||
"name": "Pintel (hard)",
|
||||
"source": "c' = -500 -200;\\n\\nA = 1 0\\n 0 1\\n 2 1\\n -1 0\\n 0 -1;\\n\\nb = 4\\n 7\\n 9\\n 0\\n 0;\\n\\nB = 1 3;"
|
||||
},
|
||||
{
|
||||
"name": "Pintel non amm 2",
|
||||
"source": "c' = -500 -200;\\n\\nA = 1 0\\n 0 1\\n 2 1\\n -1 0\\n 0 -1;\\n\\nb = 4\\n 7\\n 9\\n 0\\n 0;\\n\\nB = 1 2;"
|
||||
}
|
||||
]
|
||||
@@ -88,22 +88,28 @@ export function drawSimpleArrow(
|
||||
y1: number,
|
||||
x2: number,
|
||||
y2: number,
|
||||
size: number
|
||||
size: number,
|
||||
color: string = '#333'
|
||||
) {
|
||||
const arrowLength = Math.sqrt((x2 - x1) ** 2 + (y2 - y1) ** 2)
|
||||
|
||||
g.save()
|
||||
g.strokeStyle = color
|
||||
g.fillStyle = color
|
||||
|
||||
g.beginPath()
|
||||
g.translate(x1, y1)
|
||||
g.rotate(Math.atan2(y2 - y1, x2 - x1))
|
||||
g.moveTo(0, 0)
|
||||
g.lineTo(arrowLength, 0)
|
||||
|
||||
g.moveTo(arrowLength - size, -size)
|
||||
g.lineTo(arrowLength, 0)
|
||||
g.moveTo(arrowLength - size, +size)
|
||||
g.lineTo(arrowLength, 0)
|
||||
g.lineTo(arrowLength - size / 2, 0)
|
||||
g.stroke()
|
||||
|
||||
g.beginPath()
|
||||
g.moveTo(arrowLength, 0)
|
||||
g.lineTo(arrowLength - size, -size * 0.75)
|
||||
g.lineTo(arrowLength - size, +size * 0.75)
|
||||
g.lineTo(arrowLength, 0)
|
||||
g.fill()
|
||||
g.restore()
|
||||
}
|
||||
|
||||
@@ -118,3 +124,10 @@ export function strokeDot(g: CanvasRenderingContext2D, x: number, y: number, rad
|
||||
g.arc(x, y, radius, 0, 2 * Math.PI)
|
||||
g.stroke()
|
||||
}
|
||||
|
||||
export function strokeInfiniteLine(g: CanvasRenderingContext2D, x1: number, y1: number, angle: number) {
|
||||
g.beginPath()
|
||||
g.moveTo(x1 - Math.cos(angle) * MAX_LINE_SIZE, y1 - Math.sin(angle) * MAX_LINE_SIZE)
|
||||
g.lineTo(x1 + Math.cos(angle) * MAX_LINE_SIZE, y1 + Math.sin(angle) * MAX_LINE_SIZE)
|
||||
g.stroke()
|
||||
}
|
||||
|
||||
+137
-96
@@ -30,6 +30,7 @@ export type ProblemStatus =
|
||||
| { result: 'unbounded'; xi: Vector<Rational> }
|
||||
| { result: 'infeasible' }
|
||||
| { result: 'too-many-iterations' }
|
||||
| { result: 'wrong-starting-basis' }
|
||||
|
||||
export type ProblemOutput = {
|
||||
status: ProblemStatus
|
||||
@@ -66,6 +67,7 @@ export function computePrimalSimplexSteps(input: ProblemInput): ProblemOutput {
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
`B = ${indexSetToLatex(B)}`,
|
||||
`A_B = ${matrixToLatex(A_B)}`,
|
||||
`b_B = ${vectorToLatex(b_B)}`,
|
||||
`c^t = ${rowVectorToLatex(c)}`,
|
||||
@@ -74,144 +76,183 @@ export function computePrimalSimplexSteps(input: ProblemInput): ProblemOutput {
|
||||
|
||||
const x = A_B_inverse.apply(b_B)
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
String.raw`\bar{x}`,
|
||||
`A_B^{-1} b_B`,
|
||||
`${matrixToLatex(A_B)}^{-1} ${vectorToLatex(b_B)}`,
|
||||
`${matrixToLatex(A_B_inverse)} ${vectorToLatex(b_B)}`,
|
||||
`${vectorToLatex(x)}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
const y_B = A_B_inverse.transpose().apply(c)
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
String.raw`\bar{y}_B^t`,
|
||||
`c_B^t A_B^{-1}`,
|
||||
`${rowVectorToLatex(c)} ${matrixToLatex(A_B_inverse)}`,
|
||||
`${rowVectorToLatex(y_B)}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
const y_Zero = Vector.zero(RationalField, A.rows)
|
||||
|
||||
const y = y_Zero.with(B, y_B)
|
||||
|
||||
comments.push({ type: 'formula', content: String.raw`\implies \bar{y}^t = ${rowVectorToLatex(y)}` })
|
||||
|
||||
const I_x = activeIndices(input, x)
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
String.raw`I(\bar{x})`,
|
||||
String.raw`\{ i \in \{1, \dots, m\} \mid A_i \bar{x}_i = b_i \}`,
|
||||
indexSetToLatex(I_x),
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
const isDegenerate = I_x.length < A_B.rows
|
||||
const isDualAdmissible = y_B.getData().every(y => y.geq(RationalField.zero))
|
||||
const isDualDegenerate = y_B.getData().some(y => y.eq(RationalField.zero))
|
||||
|
||||
comments.push({
|
||||
type: 'text',
|
||||
content: `La soluzione primale è *${isDegenerate ? 'degenere' : 'non degenere'}*.`,
|
||||
})
|
||||
|
||||
comments.push({
|
||||
type: 'text',
|
||||
content: `La soluzione duale è *${isDualAdmissible ? 'ammissibile' : 'non ammissibile'}* e *${
|
||||
isDualDegenerate ? 'degenere' : 'non degenere'
|
||||
}*.`,
|
||||
})
|
||||
|
||||
if (!isDualAdmissible) {
|
||||
const h = Math.min(...y.getData().flatMap((y, i) => (y.lt(RationalField.zero) ? [i] : [])))
|
||||
const isAdmissible = A.apply(x).leq(b)
|
||||
|
||||
if (isAdmissible) {
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
// prettier-ignore
|
||||
`h`,
|
||||
String.raw`\min \{ i \in B \mid \bar{y}_i < 0 \}`,
|
||||
`${h + 1}`,
|
||||
String.raw`\bar{x}`,
|
||||
`A_B^{-1} b_B`,
|
||||
`${matrixToLatex(A_B)}^{-1} ${vectorToLatex(b_B)}`,
|
||||
`${matrixToLatex(A_B_inverse)} ${vectorToLatex(b_B)}`,
|
||||
`${vectorToLatex(x)}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
const e_h = Vector.oneHot(RationalField, A.rows, h).slice(B)
|
||||
console.log(e_h)
|
||||
|
||||
const xi = A_B_inverse.apply(e_h).neg()
|
||||
stepResult_xi = xi
|
||||
const y_B = A_B_inverse.transpose().apply(c)
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
// prettier-ignore
|
||||
`\\xi`,
|
||||
`-A_B^{-1} u_{B(h)}`,
|
||||
`${vectorToLatex(xi)}`,
|
||||
String.raw`\bar{y}_B^t`,
|
||||
`c_B^t A_B^{-1}`,
|
||||
`${rowVectorToLatex(c)} ${matrixToLatex(A_B_inverse)}`,
|
||||
`${rowVectorToLatex(y_B)}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
const N = range(0, A.rows).filter(i => !B.includes(i))
|
||||
const A_N = A.slice({ rows: N })
|
||||
const y_Zero = Vector.zero(RationalField, A.rows)
|
||||
|
||||
const A_N__xi = A_N.apply(xi)
|
||||
const y = y_Zero.with(B, y_B)
|
||||
|
||||
comments.push({ type: 'formula', content: String.raw`\implies \bar{y}^t = ${rowVectorToLatex(y)}` })
|
||||
|
||||
const I_x = activeIndices(input, x)
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
[`N = \\{1, \\dots, m\\} \\setminus B = ${indexSetToLatex(N)}`],
|
||||
[`A_N \\xi`, `${matrixToLatex(A_N)} ${vectorToLatex(xi)}`, `${vectorToLatex(A_N__xi)}`].join(' = '),
|
||||
].join(' \\qquad '),
|
||||
String.raw`I(\bar{x})`,
|
||||
String.raw`\{ i \in \{1, \dots, m\} \mid A_i \bar{x}_i = b_i \}`,
|
||||
indexSetToLatex(I_x),
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
if (!A_N__xi.getData().every(x => x.leq(RationalField.zero))) {
|
||||
const [k, lambda] = N.filter(i => A_N__xi.at(A_N.forwardRowIndices[i]).gt(RationalField.zero))
|
||||
.map<[number, Rational]>(i => [i, b.at(i).sub(A.rowAt(i).dot(x)).div(A.rowAt(i).dot(xi))])
|
||||
.reduce(([i1, lambda1], [i2, lambda2]) => (lambda1.lt(lambda2) ? [i1, lambda1] : [i2, lambda2]))
|
||||
const isDegenerate = I_x.length < A_B.rows
|
||||
const isDualAdmissible = y_B.getData().every(y => y.geq(RationalField.zero))
|
||||
const isDualDegenerate = y_B.getData().some(y => y.eq(RationalField.zero))
|
||||
|
||||
comments.push({
|
||||
type: 'text',
|
||||
content: `La soluzione primale è *${isAdmissible ? 'ammissibile' : 'non ammissibile'}* e *${
|
||||
isDegenerate ? 'degenere' : 'non degenere'
|
||||
}*.`,
|
||||
})
|
||||
|
||||
comments.push({
|
||||
type: 'text',
|
||||
content: `La soluzione duale è *${isDualAdmissible ? 'ammissibile' : 'non ammissibile'}* e *${
|
||||
isDualDegenerate ? 'degenere' : 'non degenere'
|
||||
}*.`,
|
||||
})
|
||||
|
||||
if (!isDualAdmissible) {
|
||||
const h = Math.min(...y.getData().flatMap((y, i) => (y.lt(RationalField.zero) ? [i] : [])))
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
`\\bar\\lambda`,
|
||||
`\\min_i \\left\\{ \\frac{b_i - A_i \\bar{x}}{A_i \\xi} \\; \\middle| \\; i \\in N, A_i \\xi > 0 \\right\\}`,
|
||||
`${lambda}`,
|
||||
// prettier-ignore
|
||||
`h`,
|
||||
String.raw`\min \{ i \in B \mid \bar{y}_i < 0 \}`,
|
||||
`${h + 1}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
const e_h = Vector.oneHot(RationalField, A.rows, h).slice(B)
|
||||
console.log(e_h)
|
||||
|
||||
const xi = A_B_inverse.apply(e_h).neg()
|
||||
stepResult_xi = xi
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
`k`,
|
||||
`\\argmin_i \\left\\{ \\bar\\lambda_i \\; \\middle| \\; i \\in N, A_i \\xi > 0 \\right\\}`,
|
||||
`${k + 1}`,
|
||||
// prettier-ignore
|
||||
`\\xi`,
|
||||
`-A_B^{-1} u_{B(h)}`,
|
||||
`${vectorToLatex(xi)}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
const N = range(0, A.rows).filter(i => !B.includes(i))
|
||||
const A_N = A.slice({ rows: N })
|
||||
|
||||
const A_N__xi = A_N.apply(xi)
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
`B'`,
|
||||
`B \\setminus \\{h + 1\\} \\cup \\{k + 1\\}`,
|
||||
`${indexSetToLatex([...B.filter(i => i !== h), k].toSorted())}`,
|
||||
].join(' = '),
|
||||
[`N = \\{1, \\dots, m\\} \\setminus B = ${indexSetToLatex(N)}`],
|
||||
[`A_N \\xi`, `${matrixToLatex(A_N)} ${vectorToLatex(xi)}`, `${vectorToLatex(A_N__xi)}`].join(
|
||||
' = '
|
||||
),
|
||||
].join(' \\qquad '),
|
||||
})
|
||||
|
||||
stepResult_B = [...B.filter(i => i !== h), k].toSorted()
|
||||
if (!A_N__xi.getData().every(x => x.leq(RationalField.zero))) {
|
||||
const [k, lambda] = N.filter(i => A_N__xi.at(A_N.forwardRowIndices[i]).gt(RationalField.zero))
|
||||
.map<[number, Rational]>(i => [i, b.at(i).sub(A.rowAt(i).dot(x)).div(A.rowAt(i).dot(xi))])
|
||||
.reduce(([i1, lambda1], [i2, lambda2]) => (lambda1.lt(lambda2) ? [i1, lambda1] : [i2, lambda2]))
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
`\\bar\\lambda`,
|
||||
`\\min_i \\left\\{ \\frac{b_i - A_i \\bar{x}}{A_i \\xi} \\; \\middle| \\; i \\in N, A_i \\xi > 0 \\right\\}`,
|
||||
`${lambda}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
`k`,
|
||||
`\\argmin_i \\left\\{ \\bar\\lambda_i \\; \\middle| \\; i \\in N, A_i \\xi > 0 \\right\\}`,
|
||||
`${k + 1}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
`B'`,
|
||||
`B \\setminus \\{${h + 1}\\} \\cup \\{${k + 1}\\}`,
|
||||
`${indexSetToLatex([...B.filter(i => i !== h), k].toSorted())}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
stepResult_B = [...B.filter(i => i !== h), k].toSorted()
|
||||
} else {
|
||||
comments.push({ type: 'text', content: 'La soluzione è *illimitata*' })
|
||||
|
||||
status = { result: 'unbounded', xi }
|
||||
}
|
||||
} else {
|
||||
comments.push({ type: 'text', content: 'La soluzione è *illimitata*' })
|
||||
status = { result: 'unbounded', xi }
|
||||
comments.push({ type: 'text', content: 'La soluzione è *ottima*' })
|
||||
|
||||
status = { result: 'optimal', x }
|
||||
}
|
||||
} else {
|
||||
comments.push({ type: 'text', content: 'La soluzione è *ottima*' })
|
||||
status = { result: 'optimal', x }
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content: [
|
||||
String.raw`\bar{x}`,
|
||||
`A_B^{-1} b_B`,
|
||||
`${matrixToLatex(A_B)}^{-1} ${vectorToLatex(b_B)}`,
|
||||
`${matrixToLatex(A_B_inverse)} ${vectorToLatex(b_B)}`,
|
||||
`${vectorToLatex(x)}`,
|
||||
].join(' = '),
|
||||
})
|
||||
|
||||
comments.push({
|
||||
type: 'formula',
|
||||
content:
|
||||
[
|
||||
//
|
||||
`A \\bar{x}`,
|
||||
`${matrixToLatex(A)} ${vectorToLatex(x)}`,
|
||||
`${vectorToLatex(A.apply(x))}`,
|
||||
].join(' = ') + ` \\not\\leq ${vectorToLatex(b)} = b`,
|
||||
})
|
||||
|
||||
comments.push({
|
||||
type: 'text',
|
||||
content: 'In questo caso bisogna usare il metodo del *simplesso duale*.',
|
||||
})
|
||||
|
||||
status = { result: 'wrong-starting-basis' }
|
||||
}
|
||||
|
||||
steps.push({
|
||||
|
||||
@@ -26,6 +26,14 @@ export abstract class Vector<T extends FieldValue<T>> {
|
||||
return new VectorDense(this.getData().map(v => v.neg()))
|
||||
}
|
||||
|
||||
leq(vector: Vector<T>): boolean {
|
||||
if (this.size !== vector.size) {
|
||||
throw new Error('Vector dimensions do not match')
|
||||
}
|
||||
|
||||
return this.getData().every((v, i) => v.leq(vector.at(i)))
|
||||
}
|
||||
|
||||
with(indices: number[], vector: Vector<T>): Vector<T> {
|
||||
if (indices.length !== vector.size) {
|
||||
throw new Error('Vector dimensions do not match')
|
||||
|
||||
+21
-8
@@ -4,6 +4,8 @@ import { parseSafeProblemInput } from './parser-problem'
|
||||
import { DisplayProblemInput } from './DisplayProblemInput'
|
||||
import { Primale } from './Primale'
|
||||
|
||||
import exampleProblems from './example-problems.json'
|
||||
|
||||
const INITIAL_PROBLEM_INPUT = `
|
||||
c' = 500 200;
|
||||
|
||||
@@ -37,18 +39,30 @@ const useLocalStorage = <T,>(key: string, initialValue: T) => {
|
||||
}
|
||||
|
||||
const App = () => {
|
||||
const [currentProblemName, setCurrentProblemName] = useState('Pintel')
|
||||
const [savedProblems, setSavedProblems] = useLocalStorage<{ name: string; source: string }[]>('savedProblems', [
|
||||
{ name: 'Pintel', source: INITIAL_PROBLEM_INPUT },
|
||||
])
|
||||
const [currentProblemName, setCurrentProblemName] = useLocalStorage(
|
||||
'ricerca-operativa.currentProblemName',
|
||||
'Pintel'
|
||||
)
|
||||
const [savedProblems, setSavedProblems] = useLocalStorage<{ name: string; source: string }[]>(
|
||||
'ricerca-operativa.savedProblems',
|
||||
exampleProblems
|
||||
)
|
||||
|
||||
const [problemInput, setProblemInput] = useState(INITIAL_PROBLEM_INPUT)
|
||||
const [problemInput, setProblemInput] = useState(
|
||||
savedProblems.find(p => p.name === currentProblemName)?.source ?? INITIAL_PROBLEM_INPUT
|
||||
)
|
||||
|
||||
const problemValuesResult = parseSafeProblemInput(problemInput)
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Ricerca Operativa / Programmazione Lineare</h1>
|
||||
<h1>
|
||||
Ricerca Operativa / Programmazione Lineare
|
||||
<small>
|
||||
{' '}
|
||||
by <a href="aziis98.com">@aziis98</a>
|
||||
</small>
|
||||
</h1>
|
||||
<p>
|
||||
Questo sito è un progetto per il{' '}
|
||||
<a href="https://didawiki.cli.di.unipi.it/doku.php/matematica/ro/start">corso di Ricerca Operativa</a>{' '}
|
||||
@@ -113,6 +127,7 @@ const App = () => {
|
||||
rows={Math.max(problemInput.split('\n').length, 5)}
|
||||
cols={100}
|
||||
></textarea>
|
||||
|
||||
<h2>Problema di Input</h2>
|
||||
{'result' in problemValuesResult ? (
|
||||
<DisplayProblemInput problemInput={problemValuesResult.result} />
|
||||
@@ -122,8 +137,6 @@ const App = () => {
|
||||
</p>
|
||||
)}
|
||||
|
||||
<h2>Svolgimento</h2>
|
||||
|
||||
{'result' in problemValuesResult && <Primale input={problemValuesResult.result} />}
|
||||
</>
|
||||
)
|
||||
|
||||
+93
-7
@@ -26,9 +26,10 @@
|
||||
h2,
|
||||
h3,
|
||||
h4 {
|
||||
font-weight: 300;
|
||||
font-weight: 600;
|
||||
margin: 0;
|
||||
color: #222;
|
||||
color: #444;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -101,6 +102,11 @@
|
||||
font-weight: 700;
|
||||
color: #444;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 0.5em;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
|
||||
@layer components {
|
||||
@@ -115,8 +121,9 @@
|
||||
|
||||
> * {
|
||||
display: block;
|
||||
max-width: 900px;
|
||||
margin: 0.5rem auto;
|
||||
width: 900px;
|
||||
max-width: 100%;
|
||||
margin: 1rem auto;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,8 +151,21 @@
|
||||
border-radius: 1rem;
|
||||
|
||||
padding: 2rem;
|
||||
margin: 2rem auto;
|
||||
|
||||
width: 100%;
|
||||
max-width: fit-content;
|
||||
|
||||
> .title {
|
||||
grid-column: span 2;
|
||||
text-align: center;
|
||||
padding-bottom: 1rem;
|
||||
|
||||
@media (width < 768px) {
|
||||
grid-column: span 1;
|
||||
}
|
||||
}
|
||||
|
||||
> .step {
|
||||
grid-column: span 2;
|
||||
|
||||
@@ -157,6 +177,7 @@
|
||||
gap: 0;
|
||||
|
||||
@media (width < 768px) {
|
||||
grid-column: span 1;
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
@@ -169,21 +190,46 @@
|
||||
border-radius: 1rem;
|
||||
padding: 0 1rem;
|
||||
|
||||
> * {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
> * + * {
|
||||
margin-top: 0.5rem;
|
||||
margin-top: 1rem;
|
||||
}
|
||||
}
|
||||
|
||||
> .geometric-step {
|
||||
/* padding: 1rem; */
|
||||
@media (width < 768px) {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
> * {
|
||||
min-width: 30rem;
|
||||
}
|
||||
|
||||
padding: 2rem 0;
|
||||
|
||||
&:not(:first-child) {
|
||||
&:not(:nth-child(1), :nth-child(2)) {
|
||||
border-top: 1px solid #ddd;
|
||||
}
|
||||
|
||||
@media (width < 768px) {
|
||||
max-width: 100%;
|
||||
gap: 1rem;
|
||||
padding: 1rem 0;
|
||||
|
||||
> * {
|
||||
min-width: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (width < 768px) {
|
||||
grid-template-columns: 1fr;
|
||||
padding: 1rem 0 0 0;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -203,6 +249,34 @@
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.scrollable {
|
||||
display: grid;
|
||||
|
||||
> .scroll-content {
|
||||
display: grid;
|
||||
}
|
||||
|
||||
@media (width < 768px) {
|
||||
position: relative;
|
||||
|
||||
> .scroll-content {
|
||||
overflow-x: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
&::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 0.5rem;
|
||||
background: linear-gradient(to left, #0003, #0000);
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.flex-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
@@ -212,5 +286,17 @@
|
||||
> .spacer {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
@media (width < 768px) {
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
}
|
||||
|
||||
.text-center {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
|
||||
.katex-display {
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
Vendored
+5
@@ -2,3 +2,8 @@ declare module '*.css' {
|
||||
const classes: { readonly [key: string]: string }
|
||||
export default classes
|
||||
}
|
||||
|
||||
declare module '*.json' {
|
||||
const value: any
|
||||
export default value
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user