-
Notifications
You must be signed in to change notification settings - Fork 154
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Matrices Course and Gauss Solver #307
Open
kevindeland
wants to merge
40
commits into
master
Choose a base branch
from
gauss-solver-kevin
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
40 commits
Select commit
Hold shift + click to select a range
f084f03
Migrate Inverse draft from Notion into Markdown
kevindeland 96596df
Quick edits to intro
kevindeland 5da5aa0
Quick edits to Example Calculation
kevindeland 495910c
Quick Edits to Inverse:Rotation
kevindeland 780e334
Quick Edits to Inverse:Intersection
kevindeland 1a5cd72
Quick Edits to Inverse:Derive
kevindeland bb0c70b
Migrate part of Gauss draft from Notion into Markdown
kevindeland d0a1043
Prototype for Network Analysis
kevindeland e8d8a66
Draft for Inverse:LeastSquares
kevindeland 801fbb0
Draft for Gauss:ElectricBill
kevindeland 1bc5cb0
Beginnings of x-gaussian solver
kevindeland 27666b9
Can copy rows, can move left and right matrices
kevindeland 11d27d8
Fully articulated and half-implemented steps for solving 2x2
kevindeland cc84705
Gaussian: beginning to codify 3x3 matrix solution steps
kevindeland b82e878
Remove extra section for singular matrices
kevindeland 79b0623
Formula for inverse of 3x3 matrix
kevindeland 930cda1
guass map of netherlands
kevindeland 1d2016b
prepping for code
kevindeland 697a077
Merge branch 'master' into matrix-inverses
kevindeland 803d395
First pass at copying g-doc into markdown
kevindeland 2a8c0a6
todo
kevindeland d952d9d
Merge branch 'master' into matrix-inverses
kevindeland 2f936d6
hand-copied Philipp code from 'gauss-solver' branch
kevindeland aa57b90
gauss-solver: right outward arrows
kevindeland 027f6b6
Row operations working, can move output to input
kevindeland 48e9b46
Change text/display with operation
kevindeland b4096d7
Special treat when matrix is solved.
kevindeland d804136
Undo button
kevindeland 9f39d43
An attempt to add row highlighting (CSS needs work)
kevindeland e5e727f
Some CSS fixing (only looks good when fully expanded)
kevindeland de97e7d
Removed old Gaussian solver
kevindeland c862ecf
Delete left.svg
plegner 8223f18
Delete right.svg
plegner 90ee6c1
Update styles.less
plegner 132ffa2
Change storage of input/output from HTML text to number[][]
kevindeland 5fe8ceb
Fixed undo button stack
kevindeland 0e8e66a
comment out logs
kevindeland 46e1f5e
some refactoring and cleaning
kevindeland 611fef0
fixed swap function (weird bug found?)
kevindeland 7b3cb8e
gauss solver TODO comments
kevindeland File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,77 @@ | ||
|
||
@import "../../shared/variables"; | ||
|
||
x-gauss-solver { | ||
display: flex; | ||
justify-content: center; | ||
|
||
.highlighter { | ||
position: absolute; | ||
&.input { left: 50px; top: 40px; } | ||
&.output { left: 566px; top: 40px; } | ||
|
||
rect.red { fill: @red; opacity: 0.5;} | ||
rect.blue { fill: @blue; opacity: 0.5;} | ||
} | ||
|
||
.matrix { | ||
display: grid; | ||
grid-auto-rows: 36px; | ||
grid-gap: 4px; | ||
margin-top: 12px; | ||
> div { text-align: center; line-height: 36px; } | ||
} | ||
|
||
.operation { flex-grow: 1; position: relative; margin: 0 40px; max-width: 280px; } | ||
.operation x-select { display: flex; margin-bottom: 1px; } | ||
.op { | ||
background: @medium-grey; | ||
color: white; | ||
margin-right: 1px; | ||
flex: 40px 1 1; | ||
opacity: 0.6; | ||
text-align: center; | ||
cursor: pointer; | ||
&.active { opacity: 1; } | ||
&:first-child { border-top-left-radius: 6px; } | ||
&:last-child { border-top-right-radius: 6px; margin-right: 0; } | ||
} | ||
|
||
.operation-body { | ||
background: mix(@medium-grey, white); | ||
padding: 24px 0; | ||
border-radius: 0 0 6px 6px; | ||
p { text-align: center; margin: 0; } | ||
input { background: white; width: 40px; } | ||
} | ||
|
||
.connections-left { | ||
position: absolute; left: -55px; top: 10px; | ||
circle { cursor: grab; } | ||
path { stroke-width: 3px; fill: none; stroke-linecap: round; } | ||
} | ||
|
||
.connections-right { | ||
position: absolute; left: 250px; top: 10px; | ||
path { stroke-width: 3px; fill: none; stroke-linecap: round; } | ||
} | ||
|
||
.description { | ||
text-align: center; | ||
font-size: small; | ||
} | ||
|
||
.bottom { | ||
text-align: center; | ||
|
||
button { | ||
margin: 5px; | ||
padding: 5px; | ||
background: mix(@medium-grey, white, 20); | ||
border: 1px; | ||
border-color: black; | ||
cursor: pointer; | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
svg.highlighter.input(:width="(size+2)*40+4" :height="size*40+4+12") | ||
rect.red(:x="40*(3-size)" :y="(20+40*inRow1)" :width="(size+1)*40" height="36" rx="18" ry="18") | ||
rect.blue(:x="40*(3-size)" :y="(20+40*inRow2)" :width="(size+1)*40" height="36" rx="18" ry="18" :show="op !== 'multiply'") | ||
svg.highlighter.output(:width="(size+2)*40+4" :height="size*40+4+12") | ||
rect.red(x="0" :y="(20+40*outRow1)" :width="(size+1)*40" height="36" rx="18" ry="18") | ||
rect.blue(x="0" :y="(20+40*outRow2)" :width="(size+1)*40" height="36" rx="18" ry="18" :show="op === 'swap'") | ||
.matrix | ||
.operation | ||
svg.connections-left(width=100 height=120) | ||
path(:d="'M20,'+(20+40*inRow1)+'C50,'+(20+40*inRow1)+',50,60,80,60'" stroke="#d90000") | ||
circle(cx=20 :cy="20+40*inRow1" r=8 fill="#d90000") | ||
path(:d="'M20,'+(20+40*inRow2)+'C50,'+(20+40*inRow2)+',50,60,80,60'" stroke="#0f82f2" :show="op !== 'multiply'") | ||
circle(cx=20 :cy="20+40*inRow2" r=8 fill="#0f82f2" :show="op !== 'multiply'") | ||
svg.connections-right(width=100 height=120) | ||
path(:d="'M20,60C50,60,50,'+(20+40*outRow1)+',80,'+(20+40*outRow1)" stroke="#d90000") | ||
polygon(:points="'76,'+(20+40*outRow1-8)+' 76,'+(20+40*outRow1+8)+',92,'+(20+40*outRow1)" fill="#d90000") | ||
path(:d="'M20,60C50,60,50,'+(20+40*outRow2)+',80,'+(20+40*outRow2)" stroke="#0f82f2" :show="op === 'swap'") | ||
polygon(:points="'76,'+(20+40*outRow2-8)+' 76,'+(20+40*outRow2+8)+' 92,'+(20+40*outRow2)" fill="#0f82f2" :show="op === 'swap'") | ||
x-select(:bind="op") | ||
.op(value="multiply") Multiply | ||
.op(value="add") Add | ||
.op(value="swap") Swap | ||
.operation-body | ||
p(:show="op ==='add'") | ||
svg(width=16 height=16) | ||
circle(cx=8 cy=8 r=8 fill="#d90000" :show="op === 'add'") | ||
| + | ||
input(:bind="factorString") | ||
svg(width=16 height=16) | ||
circle(cx=8 cy=8 r=8 fill="#0f82f2" :show="op === 'add'") | ||
p(:show="op ==='multiply'") | ||
| x | ||
input(:bind="factorString") | ||
.text | ||
.description(:show="op ==='multiply'") Multiply a row by a non-zero factor | ||
.description(:show="op ==='add'") Add a multiple of one row to another | ||
.description(:show="op ==='swap'") Swap two rows | ||
.bottom | ||
button.apply Apply | ||
button.undo Undo | ||
.matrix |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,250 @@ | ||
|
||
|
||
import {$N, CustomElementView, ElementView, Observable, observe, register, slide} from '@mathigon/boost'; | ||
import {tabulate2D} from '@mathigon/core'; | ||
import {Point} from '@mathigon/euclid'; | ||
import {clamp} from '@mathigon/fermat'; | ||
import {Expression} from '@mathigon/hilbert'; | ||
import {Step} from '../../shared/types'; | ||
|
||
|
||
import template from './gauss-solver.pug'; | ||
|
||
// TODO: function (px) to calculate "20+40*inRow1", and add output to the model | ||
type Model = { | ||
input: number[][], output: number[][], op: string, size: number, | ||
inRow1: number, inRow2: number, outRow1: number, outRow2: number, | ||
factorString: string, factor: number | ||
}; | ||
|
||
@register('x-gauss-solver', {template}) | ||
export class GaussSolver extends CustomElementView { | ||
model!: Observable<Model>; | ||
size!: number; // Number of Matrix rows | ||
$inputCells!: ElementView[][]; // Array of all cells in the left matrix. | ||
$outputCells!: ElementView[][]; // Array of all cells in the right matrix. | ||
$step!: Step; | ||
inputStack!: number[][][]; | ||
$matrices!: ElementView[]; | ||
|
||
ready() { | ||
// TODO: instead of decimals, display fractions (now? or later?) | ||
// TODO: use "parseInput" instead of Expression.parse | ||
// TODO: use "collision detection" to prevent same row from being highlighted | ||
const input = Expression.parse(this.attr('matrix')) | ||
.evaluate({'[': (...args: number[]) => [...args] as any}) as unknown as number[][]; | ||
|
||
// size is num rows | ||
this.size = input.length; | ||
this.bindModel(observe({input, output: this.copyMatrix(input), size: this.size, inRow1: 0, inRow2: 1, outRow1: 0, outRow2: 1, factor: 1, factorString: '1'})); | ||
|
||
// Set up the input and output matrices | ||
this.$matrices = this.$$('.matrix') as ElementView[]; | ||
for (const $m of this.$matrices) $m.css('grid-template-columns', `repeat(${this.size + 1}, 36px)`); | ||
|
||
this.$inputCells = tabulate2D((i, j) => | ||
$N('div', {text: input[i][j]}, this.$matrices[0]), this.size, this.size + 1 | ||
); | ||
this.$outputCells = tabulate2D(() => $N('div', {text: 0}, this.$matrices[1]), this.size, this.size + 1); | ||
|
||
this.inputStack = [this.copyMatrix(input)]; // pushes copy of input onto stack | ||
|
||
const $circles = this.$$('.connections-left circle'); | ||
const inRows = ['inRow1', 'inRow2'] as ('inRow1'|'inRow2')[]; | ||
const outRows = ['outRow1', 'outRow2'] as ('outRow1'|'outRow2')[]; | ||
|
||
/** | ||
* When a circle is moved, update the values of "inRow1" and "inRow2" | ||
*/ | ||
for (const [i, key] of inRows.entries()) { // inRow1 | ||
slide($circles[i], { | ||
move: (p: Point) => { | ||
const row = clamp(Math.round((p.y - 20) / 40), 0, this.size - 1); | ||
// Check that this row is not equal to the other input | ||
// (I removed this feature because it makes it difficult w/ 2x2 matrix to choose which one you want to multiply) | ||
/* if (this.model.op === 'multiply' || this.model[inRows[i === 0 ? 1 : 0]] !== row)*/ | ||
// TODO: here is where row collision detection would be | ||
this.model[key] = row; | ||
console.log(`Slide: ${i}, key=${key}, row=${row}`); | ||
} | ||
}); | ||
} | ||
|
||
/** | ||
* Watch for change and update display of output cells | ||
*/ | ||
this.model.watch((state) => { | ||
console.log('update state'); | ||
// FIXME: @philipp -- without this below log statement, the model won't update when inRow2 is changed during add/swap (blue arrow) | ||
for (const [i, row] of this.$outputCells.entries()) { | ||
for (const [j, _val] of row.entries()) { | ||
this.handleOperation(state, i, j); | ||
// FIXME: @philipp is there an alternative to this? With binding? | ||
_val.text = '' + this.model.output[i][j]; | ||
} | ||
} | ||
}); | ||
|
||
/** | ||
* Watch for FACTOR change | ||
* (can coexist with other watch function) | ||
*/ | ||
this.model.watch((state) => { | ||
let _parseable = true; | ||
let expr; | ||
let value; | ||
try { | ||
// TODO: this is where the parseInput should go | ||
expr = Expression.parse(state.factorString); | ||
value = expr.evaluate() as number; | ||
this.model.factor = value; | ||
} catch (e) { | ||
_parseable = false; | ||
this.model.factor = 1; | ||
} | ||
}); | ||
|
||
// APPLY + UNDO buttons | ||
const $applyBtn = this.$('.apply'); | ||
$applyBtn?.on('click', this.actionApply.bind(this)); | ||
const $undoBtn = this.$('.undo'); | ||
$undoBtn?.on('click', this.actionUndo.bind(this)); | ||
} | ||
|
||
private copyMatrix(matrix: number[][]) { | ||
return matrix.map(row => [...row]); | ||
} | ||
|
||
private handleOperation(state: Model, i: number, j: number) { | ||
// state.inRow2 is accessed here because "multiply" is switched on the first call, | ||
// and the model only registers what is needed on the first call | ||
const _inRow2 = state.inRow2; | ||
switch (state.op) { | ||
case 'multiply': | ||
this.handleMultiply(state, i, j); | ||
break; | ||
case 'add': | ||
this.handleAdd(state, i, j); | ||
break; | ||
case 'swap': | ||
this.model.outRow1 = _inRow2; | ||
this.model.outRow2 = state.inRow1; | ||
this.handleSwap(state, i, j); | ||
break; | ||
} | ||
} | ||
|
||
private handleMultiply(state: Model, i: number, j: number) { | ||
if (state.factor && i === state.inRow1) { | ||
const m = this.model.input[state.inRow1][j]; | ||
this.model.output[i][j] = state.factor * m; | ||
} else { | ||
this.model.output[i][j] = this.model.input[i][j]; | ||
} | ||
this.model.outRow1 = this.model.inRow1; | ||
} | ||
|
||
private handleAdd(state: Model, i: number, j: number) { | ||
this.model.output[i][j] = this.model.input[i][j]; | ||
if (state.factor && i === state.outRow1) { | ||
const c1 = this.model.input[state.inRow1][j]; | ||
const c2 = this.model.input[state.inRow2][j]; | ||
const write = c1 + state.factor * c2; | ||
this.model.output[i][j] = write; | ||
|
||
} else { | ||
this.model.output[i][j] = this.model.input[i][j]; | ||
} | ||
this.model.outRow1 = this.model.inRow1; | ||
} | ||
|
||
private handleSwap(state: Model, i: number, j: number) { | ||
// FIXME: swap 2 (on update model) | ||
this.model.output[i][j] = this.model.input[i][j]; | ||
if (i === state.inRow1) { | ||
this.model.output[i][j] = this.model.input[state.inRow2][j]; | ||
} else if (i === state.inRow2) { | ||
this.model.output[i][j] = this.model.input[state.inRow1][j]; | ||
} | ||
} | ||
|
||
/** | ||
* Apply the Action button | ||
*/ | ||
private actionApply() { | ||
// swapping input and output | ||
for (const [i, row] of this.$inputCells.entries()) { | ||
for (const [j, val] of row.entries()) { | ||
// move output to input, and update | ||
this.model.input[i][j] = this.model.output[i][j]; | ||
val.text = '' + this.model.input[i][j]; | ||
|
||
// apply operation to get output values, and update | ||
this.handleOperation(this.model, i, j); | ||
this.$outputCells[i][j].text = '' + this.model.output[i][j]; | ||
} | ||
} | ||
|
||
// TODO: here is where the animation goes | ||
// TODO: fade out all elements except for the output matrix | ||
// TODO: also do this with other boxes | ||
// TODO: slide the output matrix to the left, to replace input matrix | ||
// TODO: Input box reverts to default (Multiply by 1) | ||
this.$matrices[0].exit('fade'); | ||
this.inputStack.push(this.copyMatrix(this.model.input)); | ||
|
||
if (this.checkForSolvedIdentity()) { | ||
this.$step.addHint('correct'); | ||
} | ||
} | ||
|
||
// TODO: replace this with a "Reset" Button | ||
/** | ||
* Apply the Undo button | ||
*/ | ||
private actionUndo() { | ||
if (this.inputStack.length === 1) return; | ||
this.inputStack.pop(); | ||
|
||
this.model.input = this.copyMatrix(this.inputStack[this.inputStack.length - 1]); | ||
for (const [i, row] of this.$inputCells.entries()) { | ||
for (const [j, val] of row.entries()) { | ||
val.text = '' + this.model.input[i][j]; | ||
|
||
// apply operation to get output values, and update | ||
this.handleOperation(this.model, i, j); | ||
this.$outputCells[i][j].text = '' + this.model.output[i][j]; | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* Check if the cells in the input matrix equal the identity. | ||
*/ | ||
checkForSolvedIdentity(): boolean { | ||
const numRows = this.$inputCells.length; | ||
|
||
for (let i = 0; i < numRows; i++) { | ||
const values = this.$inputCells[i]; | ||
// i is also the expectedOneIndex | ||
|
||
// check all values | ||
for (let j = 0; j < numRows; j++) { | ||
if (j === i) { | ||
if (values[j].text != '1') { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We should avoid storing state in the DOM. Rather than accessing |
||
// diagonal should be 1 | ||
return false; | ||
} | ||
} else if (values[j].text != '0') { | ||
// non-diag should be 0 | ||
return false; | ||
} | ||
} | ||
} | ||
return true; | ||
} | ||
|
||
bindStep($step: Step) { | ||
this.$step = $step; | ||
} | ||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think the output rows need to be interactive right? They are the same (or swapped) as the input rows