From 28ed11a8b2247987e3cc589ab9c5dbd0a4ff2c00 Mon Sep 17 00:00:00 2001 From: Viktor Yudov Date: Sat, 17 Jun 2023 00:01:24 +0300 Subject: [PATCH 1/4] Implement Convolution2D with simple visualization --- .../convolution2d/convolution2d.tsx | 86 ++++++++++++++++++ src/visualizers/convolution2d/matrix.tsx | 88 +++++++++++++++++++ src/visualizers/convolution2d/render.tsx | 70 +++++++++++++++ src/visualizers/convolution2d/start.tsx | 31 +++++++ 4 files changed, 275 insertions(+) create mode 100644 src/visualizers/convolution2d/convolution2d.tsx create mode 100644 src/visualizers/convolution2d/matrix.tsx create mode 100644 src/visualizers/convolution2d/render.tsx create mode 100644 src/visualizers/convolution2d/start.tsx diff --git a/src/visualizers/convolution2d/convolution2d.tsx b/src/visualizers/convolution2d/convolution2d.tsx new file mode 100644 index 0000000..ed6310b --- /dev/null +++ b/src/visualizers/convolution2d/convolution2d.tsx @@ -0,0 +1,86 @@ +import { bind, here } from "../../lib"; +import { Matrix, PaddingType, PaddedMatrix, emptyMatrix } from "./matrix"; +import { range, sumBy } from "lodash"; + +export const convolution2d = async ( + input: Matrix, + kernel: Matrix, + padding: number, + step: number, + paddingType: PaddingType, +) => { + const matrix = new Mutable(input); + const resultHeight = sizeAfterConvolution(input.height, kernel.height, padding, step); + const resultWidth = sizeAfterConvolution(input.width, kernel.width, padding, step); + const result = emptyMatrix(resultHeight, resultWidth); + + bind("matrix", matrix); + bind("kernel", kernel); + bind("result", result); + + await here("start"); + matrix.value = new PaddedMatrix(matrix.value, padding, paddingType); + await here("add_padding", padding); + for (let y = 0; y < result.height; y++) { + for (let x = 0; x < result.width; x++) { + const sum = sumBy(range(kernel.height), ky => + sumBy(range(kernel.width), kx => + kernel.get(ky, kx) * matrix.value.get(y * step + ky, x * step + kx) + ) + ); + result.set(y, x, sum); + + const meta: ConvolutionStepMeta = { + inputXfrom: x * step, + inputXto: x * step + kernel.width, + inputYfrom: y * step, + inputYto: y * step + kernel.height, + outputX: x, + outputY: y, + }; + await here("convolution_step", meta); + } + } + await here("done"); +} + +const sizeAfterConvolution = (size: number, kernelSize: number, padding: number, step: number) => + Math.floor((size - kernelSize + 2 * padding) / step) + 1; + +// TODO: move to lib? +class Mutable { + constructor(public value: T) {} +} + +export type Convolution2DState = { + matrix: Mutable, + kernel: Matrix, + padding: number, + step: number, + paddingType: PaddingType, + result: Matrix, +} + +export type Convolution2DEvent = Start | Done | AddPaddingEvent | ConvolutionStepEvent; + +type Start = { type: "start" } // TODO: move to lib? +type Done = { type: "done" } // TODO: move to lib? +type AddPaddingEvent = { + type: "add_padding", + padding: number, +} +type ConvolutionStepEvent = { + type: "convolution_step", + meta: ConvolutionStepMeta, +} + +export type ConvolutionStepMeta = { + inputXfrom: number, + inputXto: number, + inputYfrom: number, + inputYto: number, + outputX: number, + outputY: number, +} + +export type Convolution2DArguments = [Matrix, Matrix, number, number, PaddingType]; diff --git a/src/visualizers/convolution2d/matrix.tsx b/src/visualizers/convolution2d/matrix.tsx new file mode 100644 index 0000000..0427edc --- /dev/null +++ b/src/visualizers/convolution2d/matrix.tsx @@ -0,0 +1,88 @@ +// TODO: move to lib? + +import { constant, times } from "lodash"; + +export interface Matrix { + height: number; + width: number; + get: (row: number, column: number) => number; +} + +export const enum PaddingType { + Reflect, Wrap, Edge, Zero +} + +export class ArrayMatrix implements Matrix { + height: number; + width: number; + + constructor(private array: number[][]) { + this.height = array.length; + this.width = array[0].length; + } + + get(row: number, column: number) { + return this.array[row][column]; + } + + set(row: number, column: number, value: number) { + this.array[row][column] = value; + } +} + +export class PaddedMatrix implements Matrix { + height: number; + width: number; + + constructor( + private matrix: Matrix, + public padding: number, + public paddingType: PaddingType, + ) { + this.height = matrix.height + 2 * padding; + this.width = matrix.width + 2 * padding; + } + + get(row: number, column: number) { + return this.matrix.get( + recalculateIndex(row, this.matrix.height, this.padding, this.paddingType), + recalculateIndex(column, this.matrix.width, this.padding, this.paddingType), + ); + } +} + +export const emptyMatrix = (height: number, width: number): ArrayMatrix => { + console.log(times(width, 0)); + return new ArrayMatrix(times(height, () => times(width, constant(0)))); +} + +const recalculateIndex = ( + index: number, + length: number, + padding: number, + paddingType: PaddingType +) => { + const correctedIndex = index - padding; + if (correctedIndex >= 0 && correctedIndex < length) { + return correctedIndex; + } + switch (paddingType) { + case PaddingType.Reflect: + return reflectIndex(correctedIndex, length); + case PaddingType.Wrap: + return wrapIndex(correctedIndex, length); + case PaddingType.Edge: + return edgeIndex(correctedIndex, length); + case PaddingType.Zero: + return 0; + } +} + +const reflectIndex = (index: number, length: number) => + index < 0 ? -index : 2 * length - index - 2; + +const wrapIndex = (index: number, length: number) => + index < 0 ? (index % length) + length : index % length; + +const edgeIndex = (index: number, length: number) => + index < 0 ? 0 : length - 1; diff --git a/src/visualizers/convolution2d/render.tsx b/src/visualizers/convolution2d/render.tsx new file mode 100644 index 0000000..53a8f76 --- /dev/null +++ b/src/visualizers/convolution2d/render.tsx @@ -0,0 +1,70 @@ +import { Convolution2DEvent, Convolution2DState, ConvolutionStepMeta } from "./convolution2d"; +import { Matrix } from "./matrix"; +import { range } from "lodash"; + +export type RenderProps = { + curState: Convolution2DState; + curEvent: Convolution2DEvent; +} + +export const Convolution2DRender = ({ curState, curEvent }) => { + console.log("Rendering", curState, curEvent); + const matrix = curState?.matrix.value; + console.log(matrix); + switch (curEvent?.name) { + case "convolution_step": + const [meta]: [ConvolutionStepMeta] = curEvent.args; + return
+ Input: + {matrixRender(matrix, 0, (y, x) => + meta.inputYfrom <= y && y < meta.inputYto && + meta.inputXfrom <= x && x < meta.inputXto)} + Kernel: + {matrixRender(curState?.kernel)} + Result: + {matrixRender(curState?.result, 0, (y, x) => + meta.outputY === y && meta.outputX === x)} +
; + case "add_padding": + const [padding]: [number] = curEvent.args; + return
+ Input: + {matrixRender(matrix, padding)} + Kernel: + {matrixRender(curState?.kernel)} + Result: + {matrixRender(curState?.result)} +
; + default: + return curState &&
+ Input: + {matrixRender(matrix)} + Kernel: + {matrixRender(curState?.kernel)} + Result: + {matrixRender(curState?.result)} +
; + } +} + +const matrixRender = ( + matrix: Matrix, + padding: number = 0, + isActive: (y: number, x: number) => boolean = () => false, +) => { + const isPadding = (row: number, col: number) => + row < padding || row >= matrix.height - padding || + col < padding || col >= matrix.width - padding; + return + + {matrix && range(matrix.height).map(y => + {range(matrix.width).map(x => + + )} + )} + +
{matrix.get(y, x)}
; +} diff --git a/src/visualizers/convolution2d/start.tsx b/src/visualizers/convolution2d/start.tsx new file mode 100644 index 0000000..392495e --- /dev/null +++ b/src/visualizers/convolution2d/start.tsx @@ -0,0 +1,31 @@ +import { Convolution2DArguments } from "./convolution2d" +import { PaddingType, ArrayMatrix } from "./matrix" + +type Props = { + doStart: (args: Convolution2DArguments, noStop: boolean) => void +} + +const args: Convolution2DArguments = [ + new ArrayMatrix([ + [5, 4, 3, 2, 1], + [4, 3, 2, 1, 0], + [3, 2, 1, 0, 1], + [2, 1, 0, 1, 2], + [1, 0, 1, 2, 3], + ]), + new ArrayMatrix([ + [0, 1, 0], + [1, 1, 1], + [0, 1, 0], + ]), + 2, + 3, + PaddingType.Edge, +] + +export const Convolution2DStarter = ({ doStart}: Props) => { + return
+ + +
; +} From 52d86640fc50428fc3908e1f4475138f6e1c7916 Mon Sep 17 00:00:00 2001 From: Viktor Yudov Date: Sat, 17 Jun 2023 01:09:54 +0300 Subject: [PATCH 2/4] Add index.tsx to convolution2d --- .../convolution2d/convolution2d.tsx | 21 ++++++++++++------- src/visualizers/convolution2d/index.tsx | 21 +++++++++++++++++++ 2 files changed, 35 insertions(+), 7 deletions(-) create mode 100644 src/visualizers/convolution2d/index.tsx diff --git a/src/visualizers/convolution2d/convolution2d.tsx b/src/visualizers/convolution2d/convolution2d.tsx index ed6310b..d334e4a 100644 --- a/src/visualizers/convolution2d/convolution2d.tsx +++ b/src/visualizers/convolution2d/convolution2d.tsx @@ -1,4 +1,4 @@ -import { bind, here } from "../../lib"; +import { bind, here } from "./"; import { Matrix, PaddingType, PaddedMatrix, emptyMatrix } from "./matrix"; import { range, sumBy } from "lodash"; @@ -63,15 +63,22 @@ export type Convolution2DState = { export type Convolution2DEvent = Start | Done | AddPaddingEvent | ConvolutionStepEvent; -type Start = { type: "start" } // TODO: move to lib? -type Done = { type: "done" } // TODO: move to lib? +// TODO: move Start and Done to lib? +type Start = { + name: "start", + args: [], +} +type Done = { + name: "done", + args: [], +} type AddPaddingEvent = { - type: "add_padding", - padding: number, + name: "add_padding", + args: [padding: number], } type ConvolutionStepEvent = { - type: "convolution_step", - meta: ConvolutionStepMeta, + name: "convolution_step", + args: [meta: ConvolutionStepMeta], } export type ConvolutionStepMeta = { diff --git a/src/visualizers/convolution2d/index.tsx b/src/visualizers/convolution2d/index.tsx new file mode 100644 index 0000000..287084d --- /dev/null +++ b/src/visualizers/convolution2d/index.tsx @@ -0,0 +1,21 @@ +import { AlgorithmManifest } from "../../lib/manifest"; +import { RuntimeStore } from "../../lib/store"; +import { Convolution2DArguments, Convolution2DEvent, Convolution2DState, convolution2d } from "./convolution2d"; +import { Convolution2DRender } from "./render"; +import { Convolution2DStarter } from "./start"; + +export const manifest: AlgorithmManifest = { + algo: convolution2d, + startComponent: Convolution2DStarter, + renderComponent: Convolution2DRender, +} + +export const globalStore = new RuntimeStore(convolution2d); + +export const bind = (name: keyof Convolution2DState, value: Convolution2DState[keyof Convolution2DState]) => { + globalStore.bind(name, value); +} + +export const here = async (name: Convolution2DEvent["name"], ...args: Convolution2DEvent["args"]): Promise => { + return globalStore.here(name, ...args); +} \ No newline at end of file From f59d239e4ae438103fed8a4992a057b746411f1c Mon Sep 17 00:00:00 2001 From: Maxim Date: Tue, 20 Jun 2023 01:10:18 +0300 Subject: [PATCH 3/4] Add type to starter --- src/App.tsx | 8 ++-- src/lib/hooks.tsx | 2 +- src/visualizers/convolution2d/matrix.tsx | 6 ++- src/visualizers/convolution2d/start.tsx | 49 +++++++++++++++--------- 4 files changed, 39 insertions(+), 26 deletions(-) diff --git a/src/App.tsx b/src/App.tsx index 112cdea..5bf8093 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ -import { BubbleSortStarter } from "./visualizers/bubble-sort/start"; -import { BubbleSortRender } from "./visualizers/bubble-sort/render"; +import { Convolution2DStarter } from "./visualizers/convolution2d/start"; +import { Convolution2DRender } from "./visualizers/convolution2d/render"; import { useVisualizer } from "./lib/hooks"; import { useEffect, useState } from "react"; @@ -48,8 +48,8 @@ const App = () => { Use arrow keys to navigate - - + +
{events && events.map((x, i) => { return
diff --git a/src/lib/hooks.tsx b/src/lib/hooks.tsx index 8810f14..306955d 100644 --- a/src/lib/hooks.tsx +++ b/src/lib/hooks.tsx @@ -1,5 +1,5 @@ import { useSyncExternalStore } from "react"; -import { globalStore } from "../visualizers/bubble-sort"; +import { globalStore } from "../visualizers/convolution2d"; export const useVisualizer = () => { return useSyncExternalStore(globalStore.subscribe, globalStore.getCurSnapshot); diff --git a/src/visualizers/convolution2d/matrix.tsx b/src/visualizers/convolution2d/matrix.tsx index 0427edc..ab36d76 100644 --- a/src/visualizers/convolution2d/matrix.tsx +++ b/src/visualizers/convolution2d/matrix.tsx @@ -8,8 +8,8 @@ export interface Matrix { get: (row: number, column: number) => number; } -export const enum PaddingType { - Reflect, Wrap, Edge, Zero +export enum PaddingType { + Reflect="reflect", Wrap="wrap", Edge="edge", Zero="zero" } export class ArrayMatrix implements Matrix { @@ -75,6 +75,8 @@ const recalculateIndex = ( return edgeIndex(correctedIndex, length); case PaddingType.Zero: return 0; + default: + throw Error("Unknown type: " + paddingType) } } diff --git a/src/visualizers/convolution2d/start.tsx b/src/visualizers/convolution2d/start.tsx index 392495e..854af5d 100644 --- a/src/visualizers/convolution2d/start.tsx +++ b/src/visualizers/convolution2d/start.tsx @@ -1,3 +1,4 @@ +import { useState } from "react" import { Convolution2DArguments } from "./convolution2d" import { PaddingType, ArrayMatrix } from "./matrix" @@ -5,26 +6,36 @@ type Props = { doStart: (args: Convolution2DArguments, noStop: boolean) => void } -const args: Convolution2DArguments = [ - new ArrayMatrix([ - [5, 4, 3, 2, 1], - [4, 3, 2, 1, 0], - [3, 2, 1, 0, 1], - [2, 1, 0, 1, 2], - [1, 0, 1, 2, 3], - ]), - new ArrayMatrix([ - [0, 1, 0], - [1, 1, 1], - [0, 1, 0], - ]), - 2, - 3, - PaddingType.Edge, -] - -export const Convolution2DStarter = ({ doStart}: Props) => { +export const Convolution2DStarter = ({ doStart }: Props) => { + const options = Object.values(PaddingType); + const [selectedType, setSelectedType] = useState(PaddingType.Edge); + const args: Convolution2DArguments = [ + new ArrayMatrix([ + [5, 4, 3, 2, 1], + [4, 3, 2, 1, 0], + [3, 2, 1, 0, 1], + [2, 1, 0, 1, 2], + [1, 0, 1, 2, 3], + ]), + new ArrayMatrix([ + [0, 1, 0], + [1, 1, 1], + [0, 1, 0], + ]), + 2, + 3, + selectedType, + ] + console.log(args[4]); return
+
; From d1feb50b05aaadf7b7ae77cc9a2b8177ac3cb0e6 Mon Sep 17 00:00:00 2001 From: Maxim Date: Tue, 20 Jun 2023 01:18:11 +0300 Subject: [PATCH 4/4] Null commit --- src/visualizers/convolution2d/start.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/visualizers/convolution2d/start.tsx b/src/visualizers/convolution2d/start.tsx index 854af5d..345769f 100644 --- a/src/visualizers/convolution2d/start.tsx +++ b/src/visualizers/convolution2d/start.tsx @@ -26,7 +26,7 @@ export const Convolution2DStarter = ({ doStart }: Props) => { 3, selectedType, ] - console.log(args[4]); + // console.log(args[4]); return