mirror of https://github.com/aziis98/ro-vis
You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
496 lines
14 KiB
TypeScript
496 lines
14 KiB
TypeScript
import { Katex } from './Katex'
|
|
import { fillDot, drawSemiplane, drawSimpleArrow } from './lib-v2/canvas'
|
|
import { range } from './lib-v2/math'
|
|
import { Matrix } from './lib-v2/matrix'
|
|
import { Rational } from './lib-v2/rationals'
|
|
import { ProblemComment, computePrimalSimplexSteps } from './lib-v2/ro/primal-simplex'
|
|
import { Vector } from './lib-v2/vector'
|
|
import { MiniMark } from './MiniMark'
|
|
import { ProblemInput } from './parser-problem'
|
|
|
|
// type Step = {
|
|
// B: number[]
|
|
// }
|
|
|
|
// const activeIndices = (input: ProblemInput, x: Vector<Rational>): number[] => {
|
|
// const { A, b } = input
|
|
// const A_x = A.apply(x)
|
|
|
|
// return A_x.getData().flatMap((a, i) => (a.eq(b.at(i)) ? [i] : []))
|
|
// }
|
|
|
|
const PrimalStep = ({
|
|
iter,
|
|
|
|
A,
|
|
b,
|
|
c,
|
|
B,
|
|
|
|
x,
|
|
xi,
|
|
|
|
comments,
|
|
}: {
|
|
iter: number
|
|
|
|
A: Matrix<Rational>
|
|
b: Vector<Rational>
|
|
c: Vector<Rational>
|
|
|
|
B: number[]
|
|
x?: Vector<Rational>
|
|
xi?: Vector<Rational>
|
|
|
|
comments: ProblemComment[]
|
|
}) => {
|
|
return (
|
|
<div class="step">
|
|
<div class="algebraic-step">
|
|
<div class="row">
|
|
<p>
|
|
Iterazione <strong>{iter + 1}</strong> dell'algoritmo
|
|
</p>
|
|
</div>
|
|
|
|
{comments.map(comment =>
|
|
comment.type === 'formula' ? (
|
|
<div class="row">
|
|
<Katex formula={comment.content} />
|
|
</div>
|
|
) : (
|
|
<div class="row">
|
|
<MiniMark content={comment.content} />
|
|
</div>
|
|
)
|
|
)}
|
|
</div>
|
|
<div class="geometric-step">
|
|
<PrimalCanvas
|
|
{...{
|
|
A,
|
|
b,
|
|
c,
|
|
B,
|
|
x,
|
|
xi,
|
|
}}
|
|
/>
|
|
</div>
|
|
</div>
|
|
)
|
|
}
|
|
|
|
export const Primale = ({ input }: { input: ProblemInput }) => {
|
|
// const steps: Step[] = [{ B: input.B }]
|
|
|
|
const problemOutput = computePrimalSimplexSteps({
|
|
A: input.A,
|
|
b: input.b,
|
|
c: input.c,
|
|
B: input.B,
|
|
maxIterations: 10,
|
|
})
|
|
|
|
return (
|
|
<div class="steps">
|
|
{problemOutput.steps.map((step, iter) => (
|
|
<PrimalStep
|
|
{...{
|
|
iter,
|
|
|
|
A: input.A,
|
|
b: input.b,
|
|
c: input.c,
|
|
|
|
B: step.B,
|
|
x: step.x,
|
|
xi: step.xi,
|
|
|
|
comments: step.comments,
|
|
}}
|
|
/>
|
|
))}
|
|
</div>
|
|
)
|
|
}
|
|
|
|
const PrimalCanvas = ({
|
|
A,
|
|
b,
|
|
c,
|
|
B,
|
|
|
|
x,
|
|
xi,
|
|
}: {
|
|
A: Matrix<Rational>
|
|
b: Vector<Rational>
|
|
c: Vector<Rational>
|
|
B: number[]
|
|
|
|
x?: Vector<Rational>
|
|
xi?: Vector<Rational>
|
|
}) => {
|
|
const render = ($canvas: HTMLCanvasElement | null) => {
|
|
if (!$canvas) {
|
|
return
|
|
}
|
|
|
|
$canvas.width = $canvas.offsetWidth
|
|
$canvas.height = $canvas.offsetHeight
|
|
|
|
const g = $canvas.getContext('2d')
|
|
if (!g) {
|
|
throw new Error('Could not get 2d context')
|
|
}
|
|
|
|
const width = $canvas.width
|
|
const height = $canvas.height
|
|
|
|
g.clearRect(0, 0, width, height)
|
|
g.strokeStyle = '#333'
|
|
g.lineWidth = 2
|
|
g.lineCap = 'round'
|
|
g.lineJoin = 'round'
|
|
|
|
g.fillStyle = '#333'
|
|
g.font = 'bold 16px sans-serif'
|
|
g.textAlign = 'center'
|
|
g.textBaseline = 'middle'
|
|
|
|
// // draw y axis arrow
|
|
// g.beginPath()
|
|
// g.moveTo(width / 2, height / 2)
|
|
// g.lineTo(width / 2, 5)
|
|
// g.lineTo(width / 2 - 10, 15)
|
|
// g.moveTo(width / 2, 5)
|
|
// g.lineTo(width / 2 + 10, 15)
|
|
// g.stroke()
|
|
|
|
// // draw x axis arrow
|
|
// g.beginPath()
|
|
// g.moveTo(width / 2, height / 2)
|
|
// g.lineTo(width - 5, height / 2)
|
|
// g.lineTo(width - 15, height / 2 - 10)
|
|
// g.moveTo(width - 5, height / 2)
|
|
// 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()))
|
|
// g.moveTo(0, 0)
|
|
// g.lineTo(30, 0)
|
|
// g.moveTo(30, 0)
|
|
// g.lineTo(25, -5)
|
|
// g.moveTo(30, 0)
|
|
// g.lineTo(25, 5)
|
|
// g.stroke()
|
|
// g.restore()
|
|
|
|
// g.fillStyle = '#333'
|
|
// g.font = '16px sans-serif'
|
|
// g.textAlign = 'center'
|
|
// g.textBaseline = 'middle'
|
|
// g.fillText(`A = ${A}`, width / 2, height / 2)
|
|
|
|
g.translate(width / 2, height / 2)
|
|
g.scale(width / 2, -width / 2)
|
|
g.scale(1 / 10, 1 / 10)
|
|
|
|
// draw semiplanes
|
|
|
|
// draw semiplanes not in B
|
|
range(0, A.rows)
|
|
.filter(i => !B.includes(i))
|
|
.forEach(i => {
|
|
const [a1, a2] = A.rowAt(i).getData()
|
|
const b_i = b.at(i)
|
|
|
|
drawSemiplane(g, a1.toNumber(), a2.toNumber(), b_i.toNumber())
|
|
})
|
|
|
|
// draw semiplanes in B
|
|
|
|
B.forEach(i => {
|
|
const [a1, a2] = A.rowAt(i).getData()
|
|
const b_i = b.at(i)
|
|
|
|
drawSemiplane(g, a1.toNumber(), a2.toNumber(), b_i.toNumber(), {
|
|
lineColor: '#040',
|
|
lineWidth: 3,
|
|
})
|
|
})
|
|
|
|
// draw current solution
|
|
if (x) {
|
|
const [x1, x2] = x.getData()
|
|
|
|
g.lineWidth = 30 / g.canvas.width
|
|
g.strokeStyle = 'darkgreen'
|
|
drawSimpleArrow(
|
|
g,
|
|
x1.toNumber(),
|
|
x2.toNumber(),
|
|
x1.toNumber() + c1.toNumber() / cLen,
|
|
x2.toNumber() + c2.toNumber() / cLen,
|
|
0.125
|
|
)
|
|
|
|
// draw xi
|
|
if (xi) {
|
|
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,
|
|
x1.toNumber(),
|
|
x2.toNumber(),
|
|
x1.toNumber() + xi1.toNumber() / xiLen,
|
|
x2.toNumber() + xi2.toNumber() / xiLen,
|
|
0.125
|
|
)
|
|
}
|
|
|
|
g.fillStyle = '#d44'
|
|
fillDot(g, x1.toNumber(), x2.toNumber(), 0.2)
|
|
}
|
|
}
|
|
|
|
return <canvas ref={render} />
|
|
}
|
|
|
|
// export const PrimaleStep = ({ input, step }: { input: ProblemInput; step: Step }) => {
|
|
// const { A, b, c } = input
|
|
|
|
// const rows = []
|
|
// const canvasOptions: Parameters<typeof PrimalCanvas>[0] = { A, b, c, B: step.B }
|
|
|
|
// const A_B = A.slice({ rows: step.B })
|
|
// const A_B_inverse = A_B.inverse2x2()
|
|
// const b_B = b.slice(step.B)
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// String.raw`A_B = ${matrixToLatex(A_B)}`,
|
|
// String.raw`b_B = ${vectorToLatex(b_B)}`,
|
|
// String.raw`c^t = ${rowVectorToLatex(c)}`,
|
|
// ].join(' \\qquad ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
|
|
// const x = A_B_inverse.apply(b_B)
|
|
// canvasOptions.x = x
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// String.raw`\bar{x}`,
|
|
// String.raw`A_B^{-1} b_B`,
|
|
// String.raw`${matrixToLatex(A_B)}^{-1} ${vectorToLatex(b_B)}`,
|
|
// String.raw`${matrixToLatex(A_B_inverse)} ${vectorToLatex(b_B)}`,
|
|
// String.raw`${vectorToLatex(x)}`,
|
|
// ].join(' = ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
|
|
// const y_B = A_B_inverse.transpose().apply(c)
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// String.raw`\bar{y}_B^t`,
|
|
// String.raw`c_B^t A_B^{-1}`,
|
|
// String.raw`${rowVectorToLatex(c)} ${matrixToLatex(A_B_inverse)}`,
|
|
// String.raw`${rowVectorToLatex(y_B)}`,
|
|
// ].join(' = ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
|
|
// const y_Zero = Vector.zero(RationalField, A.rows)
|
|
|
|
// const y = y_Zero.with(step.B, y_B)
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex formula={String.raw`\implies \bar{y}^t = ${rowVectorToLatex(y)}`} />
|
|
// </div>
|
|
// )
|
|
|
|
// const I_x = activeIndices(input, x)
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// String.raw`I(\bar{x})`,
|
|
// String.raw`\{ i \in \{1, \dots, m\} \mid A_i \bar{x}_i = b_i \}`,
|
|
// indexSetToLatex(I_x),
|
|
// ].join(' = ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
|
|
// 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))
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <p>
|
|
// La soluzione primale è <strong>{isDegenerate ? 'degenere' : 'non degenere'}</strong>.
|
|
// </p>
|
|
// <p>
|
|
// La soluzione duale è <strong>{isDualAdmissible ? 'ammissibile' : 'non ammissibile'}</strong> e{' '}
|
|
// <strong>{isDualDegenerate ? 'degenere' : 'non degenere'}</strong>.
|
|
// </p>
|
|
// </div>
|
|
// )
|
|
|
|
// if (!isDualAdmissible) {
|
|
// const h = Math.min(...y.getData().flatMap((y, i) => (y.lt(RationalField.zero) ? [i] : [])))
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// //
|
|
// String.raw`h`,
|
|
// String.raw`\min \{ i \in B \mid \bar{y}_i < 0 \}`,
|
|
// String.raw`${h + 1}`,
|
|
// ].join(' = ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
|
|
// const e_h = Vector.oneHot(RationalField, A.rows, h).slice(step.B)
|
|
// console.log(e_h)
|
|
|
|
// // const xi = Vec.neg(Mat.apply(A_B_inverse, e_h))
|
|
// const xi = A_B_inverse.apply(e_h).neg()
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// //
|
|
// `\\xi`,
|
|
// String.raw`-A_B^{-1} u_{B(h)}`,
|
|
// String.raw`${vectorToLatex(xi)}`,
|
|
// ].join(' = ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
|
|
// const N = range(0, A.rows).filter(i => !step.B.includes(i))
|
|
// const A_N = A.slice({ rows: N })
|
|
|
|
// const A_N__xi = A_N.apply(xi)
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// [`N = \\{1, \\dots, m\\} \\setminus B = ${indexSetToLatex(N)}`],
|
|
// [
|
|
// `A_N \\xi`,
|
|
// String.raw`${matrixToLatex(A_N)} ${vectorToLatex(xi)}`,
|
|
// String.raw`${vectorToLatex(A_N__xi)}`,
|
|
// ].join(' = '),
|
|
// ].join(' \\qquad ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
|
|
// if (!A_N__xi.getData().every(x => x.leq(RationalField.zero))) {
|
|
// const positiveIndices = N.filter(i => A_N__xi.at(A_N.forwardRowIndices[i]).gt(RationalField.zero))
|
|
|
|
// const [k, lambda] = positiveIndices
|
|
// .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]) => (lambda2.lt(lambda1) ? [i2, lambda2] : [i1, lambda1]))
|
|
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// `\\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(' = ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// `k`,
|
|
// `\\argmin_i \\left\\{ \\bar\\lambda_i \\; \\middle| \\; i \\in N, A_i \\xi > 0 \\right\\}`,
|
|
// `${k + 1}`,
|
|
// ].join(' = ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <Katex
|
|
// formula={[
|
|
// `\\implies B'`,
|
|
// `B \\setminus \\{${h + 1}\\} \\cup \\{${k + 1}\\}`,
|
|
// `${indexSetToLatex([...step.B.filter(i => i !== h), k].toSorted())}`,
|
|
// ].join(' = ')}
|
|
// />
|
|
// </div>
|
|
// )
|
|
// } else {
|
|
// rows.push(
|
|
// <div class="row">
|
|
// <p>
|
|
// La soluzione duale è <strong>illimitata</strong>.
|
|
// </p>
|
|
// </div>
|
|
// )
|
|
// }
|
|
// }
|
|
|
|
// return (
|
|
// <div class="step">
|
|
// <div class="algebraic-step">{rows}</div>
|
|
// <div class="geometric-step">
|
|
// <PrimalCanvas {...canvasOptions} />
|
|
// </div>
|
|
// </div>
|
|
// )
|
|
// }
|