diff --git a/src/App.tsx b/src/App.tsx index 90257fa..baec72e 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -53,14 +53,11 @@ import { PreperiodicPoint, similarPoints, } from './components/tans_theorem/tansTheoremUtils'; -import SimilarityAnimationCard from './components/tans_theorem/SimilarityAnimationCard'; -import PointsMenuJulia from './components/tans_theorem/PointsMenuJulia'; -import PointsMenuMandelbrot from './components/tans_theorem/PointsMenuMandelbrot'; import MapMarkerManager from './components/tans_theorem/MapMarkerManager'; -import ZoomMenu from './components/tans_theorem/ZoomMenu'; import NearestMisiurewiczCard from './components/tans_theorem/NearestMisiurewiczCard'; import SelfSimilaritySlider from './components/tans_theorem/SelfSimilaritySlider'; -import IntroCard from './components/tans_theorem/IntroDialog'; +import IntroDialog from './components/tans_theorem/IntroDialog'; +import TansTheoremProgressCard from './components/tans_theorem/TansTheoremProgressCard'; import { misiurewiczPairs } from './components/tans_theorem/MPoints'; const MISIUREWICZ_POINTS: PreperiodicPoint[] = misiurewiczPairs @@ -349,27 +346,13 @@ function App({ settings }: { settings: settingsDefinitionsType }): JSX.Element { useInterval(updateAspectRatio, 1000); const handleNearest = (xy: XYType) => { - const mPoint = findNearestMisiurewiczPoint(xy, 1000); + const mPoint = findNearestMisiurewiczPoint(xy, 10000); if (mPoint[0] !== 0 && mPoint[1] !== 0) { const p = new PreperiodicPoint(mPoint, mPoint, false); handleMisiurewiczPointSelection(p); } }; - const handleMisiurewiczGo = () => { - setAnimationState(AnimationStatus.SELECT_JULIA_POINT); - warpToPoint(mandelbrotControls, { - xy: focusedPointMandelbrot.point, - z: 1, - theta: 0, - }); - warpToPoint(juliaControls, { - xy: [0, 0], - z: 0.5, - theta: 0, - }); - }; - return ( <> @@ -399,7 +382,7 @@ function App({ settings }: { settings: settingsDefinitionsType }): JSX.Element { top: 0, }} > - { setAnimationState(AnimationStatus.SELECT_MANDELBROT_POINT); @@ -414,20 +397,12 @@ function App({ settings }: { settings: settingsDefinitionsType }): JSX.Element { AnimationStatus.ROTATE_M, AnimationStatus.ROTATE_J, ].includes(animationState) ? ( - ) : null} - {animationState === AnimationStatus.SELECT_MANDELBROT_POINT ? ( - - ) : null} - {animationState === AnimationStatus.SELECT_JULIA_POINT ? ( - { - setAnimationState(AnimationStatus.ZOOM_M); - warpToPoint(juliaControls, { - xy: focusedPointJulia.point, - z: 1, - theta: 0, - }); - }} - /> - ) : null} - {[ - AnimationStatus.ZOOM_M, - AnimationStatus.ZOOM_J, - AnimationStatus.ROTATE_M, - AnimationStatus.ROTATE_J, - ].includes(animationState) ? ( - { - return; - }} - handleQuit={handleReset} - show={true} - mandelbrotControls={mandelbrotControls} - juliaControls={juliaControls} - animationState={animationState} - setAnimationState={setAnimationState} - focusedPointMandelbrot={focusedPointMandelbrot} - focusedPointJulia={focusedPointJulia} - /> - ) : null} void; } + export interface MarkerManagerProps { show: boolean; aspectRatio: number; @@ -28,13 +29,6 @@ export interface SelectMenuProps { handleGo: () => void; } -export interface TansDialogsProps { - show: boolean; - animationState: AnimationStatus; - setAnimationState: React.Dispatch>; - handleQuit: () => void; -} - export interface PointsListProps { focusedPoint: PreperiodicPoint; points: PreperiodicPoint[]; @@ -42,22 +36,36 @@ export interface PointsListProps { handleSelection: (point: PreperiodicPoint) => void; } -export interface ZoomCardProps extends SelectMenuProps { - mandelbrotControls: ViewerControlSprings; - juliaControls: ViewerControlSprings; +export interface SelfSimilaritySliderProps { + focusedPointMandelbrot: PreperiodicPoint; + magnification: number; +} + +export interface TansTheoremProgressCardProps { animationState: AnimationStatus; - setAnimationState: React.Dispatch>; focusedPointMandelbrot: PreperiodicPoint; focusedPointJulia: PreperiodicPoint; + mandelbrotControls: ViewerControlSprings; + juliaControls: ViewerControlSprings; + setAnimationState: React.Dispatch>; + handleQuit: () => void; + pointsMandelbrot: PreperiodicPoint[]; + pointsJulia: PreperiodicPoint[]; + handlePointSelectionMandelbrot: (focusedPoint: PreperiodicPoint) => void; + handlePointSelectionJulia: (focusedPoint: PreperiodicPoint) => void; } -export interface PlayCardProps { +export interface ZoomCardProps { + animationState: AnimationStatus; focusedPointMandelbrot: PreperiodicPoint; - magnification: number; + focusedPointJulia: PreperiodicPoint; + mandelbrotControls: ViewerControlSprings; + juliaControls: ViewerControlSprings; + setAnimationState: React.Dispatch>; + handleQuit: () => void; } export interface SimilarityAnimationProps { - show: boolean; animationState: AnimationStatus; focusedPointMandelbrot: PreperiodicPoint; focusedPointJulia: PreperiodicPoint; diff --git a/src/components/tans_theorem/IntroDialog.tsx b/src/components/tans_theorem/IntroDialog.tsx index 1550828..017ee1f 100644 --- a/src/components/tans_theorem/IntroDialog.tsx +++ b/src/components/tans_theorem/IntroDialog.tsx @@ -3,7 +3,7 @@ import React from 'react'; import { SelectMenuProps } from '../../common/tans'; import { DialogContent, DialogTitle } from '../custom/DialogComponents'; -const IntroCard = (props: SelectMenuProps): JSX.Element => { +const IntroDialog = (props: SelectMenuProps): JSX.Element => { const [open, setOpen] = React.useState(true); if (props.show) { @@ -60,4 +60,4 @@ const IntroCard = (props: SelectMenuProps): JSX.Element => { ); }; -export default IntroCard; +export default IntroDialog; diff --git a/src/components/tans_theorem/PointsMenuJulia.tsx b/src/components/tans_theorem/PointsMenuJulia.tsx deleted file mode 100644 index a18d11c..0000000 --- a/src/components/tans_theorem/PointsMenuJulia.tsx +++ /dev/null @@ -1,34 +0,0 @@ -import { Card, Grow, Button } from '@material-ui/core'; -import React from 'react'; -import { SelectMenuProps } from '../../common/tans'; -import { KeyboardArrowRight } from '@material-ui/icons'; -import { KeyboardArrowLeft } from '@material-ui/icons'; - -const PointsInfoCard = (props: SelectMenuProps): JSX.Element => { - return ( - - - -
- -
-
-
- ); -}; - -export default PointsInfoCard; diff --git a/src/components/tans_theorem/PointsMenuMandelbrot.tsx b/src/components/tans_theorem/PointsMenuMandelbrot.tsx deleted file mode 100644 index 06fad02..0000000 --- a/src/components/tans_theorem/PointsMenuMandelbrot.tsx +++ /dev/null @@ -1,31 +0,0 @@ -import { Card, Grow, Button } from '@material-ui/core'; -import React from 'react'; -import { SelectMenuProps } from '../../common/tans'; -import { KeyboardArrowRight } from '@material-ui/icons'; - -const PointsInfoCard = (props: SelectMenuProps): JSX.Element => { - return ( - - - -
- -
-
-
- ); -}; - -export default PointsInfoCard; diff --git a/src/components/tans_theorem/SelfSimilaritySlider.tsx b/src/components/tans_theorem/SelfSimilaritySlider.tsx index 1b2d713..f569748 100644 --- a/src/components/tans_theorem/SelfSimilaritySlider.tsx +++ b/src/components/tans_theorem/SelfSimilaritySlider.tsx @@ -1,12 +1,12 @@ import { Card, Slider } from '@material-ui/core'; import React from 'react'; -import { PlayCardProps } from '../../common/tans'; +import { SelfSimilaritySliderProps } from '../../common/tans'; const gen = (x: number) => { const y = Math.abs(x) % 1; return x < 0 ? 1 - y : y; }; -const SelfSimilaritySlider = (props: PlayCardProps): JSX.Element => { +const SelfSimilaritySlider = (props: SelfSimilaritySliderProps): JSX.Element => { const x = Math.log(props.magnification) / Math.log(props.focusedPointMandelbrot.selfSimilarityFactorMagnitude); diff --git a/src/components/tans_theorem/SimilarityAnimationButtons.tsx b/src/components/tans_theorem/SimilarityAnimationButtons.tsx new file mode 100644 index 0000000..6ab56af --- /dev/null +++ b/src/components/tans_theorem/SimilarityAnimationButtons.tsx @@ -0,0 +1,230 @@ +import { Button } from '@material-ui/core'; +import React from 'react'; +import { ZoomCardProps } from '../../common/tans'; +import { AnimationStatus } from './AnimationFinalCard'; +import { KeyboardArrowLeft } from '@material-ui/icons'; +import { KeyboardArrowRight } from '@material-ui/icons'; +import { ZoomType, ThetaType } from '../../common/types'; +import { warpToPoint } from '../../common/utils'; + +const INITIAL_ZOOM = 1; + +const ActionText = { + '-1': 'null', + 0: 'CONFIRM', + 1: 'CONFIRM', + 2: 'MAGNIFY', + 3: 'MAGNIFY', + 4: 'ROTATE', + 5: 'ROTATE', + 6: 'null', +}; + +const SimilarityAnimationButtons = (props: ZoomCardProps): JSX.Element => { + const zoomM: ZoomType = props.focusedPointMandelbrot.factorMagnitude * INITIAL_ZOOM; + const zoomJ: ZoomType = props.focusedPointJulia.factorMagnitude * INITIAL_ZOOM; + const thetaM: ThetaType = -props.focusedPointMandelbrot.factorAngle; + const thetaJ: ThetaType = -props.focusedPointJulia.factorAngle; + + const selectMandelbrot = () => { + props.setAnimationState(AnimationStatus.SELECT_JULIA_POINT); + warpToPoint(props.mandelbrotControls, { + xy: props.focusedPointMandelbrot.point, + z: 1, + theta: 0, + }); + warpToPoint(props.juliaControls, { + xy: [0, 0], + z: 0.5, + theta: 0, + }); + }; + + const backSelectMandelbrot = () => { + props.handleQuit(); + }; + + const selectJulia = () => { + props.setAnimationState(AnimationStatus.ZOOM_M); + warpToPoint(props.juliaControls, { + xy: props.focusedPointJulia.point, + z: 1, + theta: 0, + }); + }; + + const backSelectJulia = () => { + props.setAnimationState(AnimationStatus.SELECT_MANDELBROT_POINT); + warpToPoint(props.mandelbrotControls, { + xy: props.focusedPointMandelbrot.point, + z: 1, + theta: 0, + }); + warpToPoint(props.juliaControls, { + xy: [0, 0], + z: 0.5, + theta: 0, + }); + }; + + const magnifyMandelbrot = () => { + props.setAnimationState(AnimationStatus.ZOOM_J); + + warpToPoint(props.mandelbrotControls, { + xy: props.focusedPointMandelbrot.point, + z: zoomM, + theta: 0, + }); + }; + + const backMagnifyMandelbrot = () => { + props.setAnimationState(AnimationStatus.SELECT_JULIA_POINT); + + warpToPoint(props.juliaControls, { + xy: [0, 0], + z: 0.5, + theta: 0, + }); + }; + + const magnifyJulia = () => { + props.setAnimationState(AnimationStatus.ROTATE_M); + + warpToPoint(props.juliaControls, { + xy: props.focusedPointJulia.point, + z: zoomJ, + theta: 0, + }); + }; + + const backMagnifyJulia = () => { + props.setAnimationState(AnimationStatus.ZOOM_M); + + warpToPoint(props.mandelbrotControls, { + xy: props.focusedPointMandelbrot.point, + z: 1, + theta: 0, + }); + }; + + const rotateMandelbrot = () => { + props.setAnimationState(AnimationStatus.ROTATE_J); + + warpToPoint(props.mandelbrotControls, { + xy: props.focusedPointMandelbrot.point, + z: zoomM, + theta: thetaM, + }); + }; + + const backRotateMandelbrot = () => { + props.setAnimationState(AnimationStatus.ZOOM_J); + + warpToPoint(props.juliaControls, { + xy: props.focusedPointJulia.point, + z: 1, + theta: 0, + }); + }; + + const rotateJulia = () => { + props.setAnimationState(AnimationStatus.PLAY); + + warpToPoint(props.juliaControls, { + xy: props.focusedPointJulia.point, + z: zoomJ, + theta: thetaJ, + }); + }; + + const backRotateJulia = () => { + props.setAnimationState(AnimationStatus.ROTATE_M); + + warpToPoint(props.mandelbrotControls, { + xy: props.focusedPointMandelbrot.point, + z: zoomM, + theta: 0, + }); + }; + + const onClick = () => { + switch (props.animationState) { + case AnimationStatus.SELECT_MANDELBROT_POINT: + backSelectMandelbrot(); + break; + case AnimationStatus.SELECT_JULIA_POINT: + backSelectJulia(); + break; + case AnimationStatus.ZOOM_M: + backMagnifyMandelbrot(); + break; + case AnimationStatus.ZOOM_J: + backMagnifyJulia(); + break; + case AnimationStatus.ROTATE_M: + backRotateMandelbrot(); + break; + case AnimationStatus.ROTATE_J: + backRotateJulia(); + break; + default: + break; + } + }; + + return ( +
+ {props.animationState === AnimationStatus.SELECT_MANDELBROT_POINT ? ( + + ) : ( + + )} +
+ +
+
+ ); +}; + +export default SimilarityAnimationButtons; diff --git a/src/components/tans_theorem/SimilarityAnimationCard.tsx b/src/components/tans_theorem/SimilarityAnimationStepper.tsx similarity index 71% rename from src/components/tans_theorem/SimilarityAnimationCard.tsx rename to src/components/tans_theorem/SimilarityAnimationStepper.tsx index 15180da..8789339 100644 --- a/src/components/tans_theorem/SimilarityAnimationCard.tsx +++ b/src/components/tans_theorem/SimilarityAnimationStepper.tsx @@ -1,10 +1,8 @@ import { Button, - Card, Stepper, StepLabel, Step, - Grow, IconButton, Dialog, Typography, @@ -31,7 +29,7 @@ interface SimpleDialogProps { onClose: (value: string) => void; } -const SimilarityAnimationCard = (props: SimilarityAnimationProps): JSX.Element => { +const SimilarityAnimationStepper = (props: SimilarityAnimationProps): JSX.Element => { function getSteps( c: PreperiodicPoint, cj: PreperiodicPoint, @@ -48,7 +46,7 @@ const SimilarityAnimationCard = (props: SimilarityAnimationProps): JSX.Element = `${formatComplexNumber(c.point)} `} + displayText={(c) => formatComplexNumber(c.point)} handleSelection={props.handlePointSelectionMandelbrot} />, ], @@ -230,89 +228,87 @@ const SimilarityAnimationCard = (props: SimilarityAnimationProps): JSX.Element = }; return ( - - + setExpanded(!expanded)} > - setExpanded(!expanded)} - > - {expanded ? : } - - {expanded ? ( - - {steps.map((label) => { - const stepProps: { completed?: boolean } = {}; - const labelProps: { optional?: React.ReactNode } = {}; - labelProps.optional = ( -
- {label[1]} - {label[0][0] !== 'S' ? ( - - ) : null} -
- ); - return ( - - {label[0]} - - ); - })} -
- ) : ( - - {steps.map((label) => { - const stepProps: { completed?: boolean } = {}; - const labelProps: { optional?: React.ReactNode } = {}; - labelProps.optional = ( -
- {label[1]} - {label[0][0] !== 'S' ? ( - - ) : null} -
- ); - return steps[props.animationState] === label ? ( - - {label[0]} - - ) : ( - - - - ); - })} -
- )} - -
-
+ {expanded ? : } + + {expanded ? ( + + {steps.map((label) => { + const stepProps: { completed?: boolean } = {}; + const labelProps: { optional?: React.ReactNode } = {}; + labelProps.optional = ( +
+ {label[1]} + {label[0][0] !== 'S' ? ( + + ) : null} +
+ ); + return ( + + {label[0]} + + ); + })} +
+ ) : ( + + {steps.map((label) => { + const stepProps: { completed?: boolean } = {}; + const labelProps: { optional?: React.ReactNode } = {}; + labelProps.optional = ( +
+ {label[1]} + {label[0][0] !== 'S' ? ( + + ) : null} +
+ ); + return steps[props.animationState] === label ? ( + + {label[0]} + + ) : ( + + + + ); + })} +
+ )} + + ); }; -export default SimilarityAnimationCard; +export default SimilarityAnimationStepper; diff --git a/src/components/tans_theorem/TansTheoremProgressCard.tsx b/src/components/tans_theorem/TansTheoremProgressCard.tsx new file mode 100644 index 0000000..1a117ce --- /dev/null +++ b/src/components/tans_theorem/TansTheoremProgressCard.tsx @@ -0,0 +1,46 @@ +import { Card } from '@material-ui/core'; +import React from 'react'; +import { TansTheoremProgressCardProps } from '../../common/tans'; +import SimilarityAnimationStepper from './SimilarityAnimationStepper'; +import SimilarityAnimationButtons from './SimilarityAnimationButtons'; +import { warpToPoint } from '../../common/utils'; + +const TansTheoremProgressCard = (props: TansTheoremProgressCardProps): JSX.Element => { + return ( + + { + props.handlePointSelectionMandelbrot(c); + warpToPoint(props.mandelbrotControls, { + xy: c.point, + z: c.factorMagnitude, + theta: 0, + }); + }} + handlePointSelectionJulia={props.handlePointSelectionJulia} + /> + + + ); +}; + +export default TansTheoremProgressCard; diff --git a/src/components/tans_theorem/ZoomMenu.tsx b/src/components/tans_theorem/ZoomMenu.tsx deleted file mode 100644 index af177f7..0000000 --- a/src/components/tans_theorem/ZoomMenu.tsx +++ /dev/null @@ -1,116 +0,0 @@ -import { Card, Button } from '@material-ui/core'; -import React from 'react'; -import { ZoomCardProps } from '../../common/tans'; -import { AnimationStatus } from './AnimationFinalCard'; -import { KeyboardArrowLeft } from '@material-ui/icons'; -import { KeyboardArrowRight } from '@material-ui/icons'; -import { ZoomType, ThetaType } from '../../common/types'; -import { warpToPoint } from '../../common/utils'; - -const INITIAL_ZOOM = 1; - -const ZoomMenu = (props: ZoomCardProps): JSX.Element => { - const ActionText = { - '-1': 'null', - 0: 'null', - 1: 'null', - 2: 'MAGNIFY', - 3: 'MAGNIFY', - 4: 'ROTATE', - 5: 'ROTATE', - 6: 'null', - }; - - const zoomMandelbrot = () => { - props.setAnimationState(AnimationStatus.ZOOM_J); - const zoomM: ZoomType = props.focusedPointMandelbrot.factorMagnitude * INITIAL_ZOOM; - - warpToPoint(props.mandelbrotControls, { - xy: props.focusedPointMandelbrot.point, - z: zoomM, - theta: 0, - }); - }; - - const zoomJulia = () => { - props.setAnimationState(AnimationStatus.ROTATE_M); - - const zoomJ: ZoomType = props.focusedPointJulia.factorMagnitude * INITIAL_ZOOM; - - warpToPoint(props.juliaControls, { - xy: props.focusedPointJulia.point, - z: zoomJ, - theta: 0, - }); - }; - - const rotateMandelbrot = () => { - props.setAnimationState(AnimationStatus.ROTATE_J); - - const zoomM: ZoomType = props.focusedPointMandelbrot.factorMagnitude * INITIAL_ZOOM; - const thetaM: ThetaType = -props.focusedPointMandelbrot.factorAngle; - - warpToPoint(props.mandelbrotControls, { - xy: props.focusedPointMandelbrot.point, - z: zoomM, - theta: thetaM, - }); - }; - - const rotateJulia = () => { - props.setAnimationState(AnimationStatus.PLAY); - - const zoomJ: ZoomType = props.focusedPointJulia.factorMagnitude * INITIAL_ZOOM; - const thetaJ: ThetaType = -props.focusedPointJulia.factorAngle; - - warpToPoint(props.juliaControls, { - xy: props.focusedPointJulia.point, - z: zoomJ, - theta: thetaJ, - }); - }; - - return ( - - -
- -
-
- ); -}; - -export default ZoomMenu; diff --git a/src/components/tans_theorem/tansTheoremUtils.tsx b/src/components/tans_theorem/tansTheoremUtils.tsx index 8ffea8a..2300fdf 100644 --- a/src/components/tans_theorem/tansTheoremUtils.tsx +++ b/src/components/tans_theorem/tansTheoremUtils.tsx @@ -1,16 +1,8 @@ -import { Button, Card } from '@material-ui/core'; import { ViewerControlSprings, XYType } from '../../common/types'; -import React from 'react'; export const MAX_DEPTH = 4; -const MAX_PREPERIOD = 1000; - -export enum OrbitFlag { - Divergent, - Cyclic, - Acyclic, -} +const MAX_PREPERIOD = 30; const equal = (a: XYType, b: XYType, tolerance = 1e-10): boolean => { const d = sub(a, b); @@ -19,53 +11,39 @@ const equal = (a: XYType, b: XYType, tolerance = 1e-10): boolean => { return dx < tolerance && dy < tolerance; }; -function magnitude(p: XYType): number { - return Math.sqrt(p[0] * p[0] + p[1] * p[1]); -} - -const add = function (a: XYType, b: XYType): XYType { - return [a[0] + b[0], a[1] + b[1]]; -}; - -const sub = function (a: XYType, b: XYType): XYType { - return [a[0] - b[0], a[1] - b[1]]; -}; +const add = (a: XYType, b: XYType): XYType => [a[0] + b[0], a[1] + b[1]]; +const sub = (a: XYType, b: XYType): XYType => [a[0] - b[0], a[1] - b[1]]; +const magnitude = (c: XYType): number => Math.sqrt(c[0] * c[0] + c[1] * c[1]); -const mult = function (a: XYType, b: XYType): XYType { - return [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]]; -}; +const mult = (a: XYType, b: XYType): XYType => [ + a[0] * b[0] - a[1] * b[1], + a[0] * b[1] + a[1] * b[0], +]; -const divide = function (x: XYType, y: XYType): XYType { - const d = Math.pow(y[0], 2) + Math.pow(y[1], 2); +const divide = (x: XYType, y: XYType): XYType => { + const d = y[0] * y[0] + y[1] * y[1]; return [(x[0] * y[0] + x[1] * y[1]) / d, (x[1] * y[0] - x[0] * y[1]) / d]; }; -const square = (c: XYType): XYType => { - return [Math.pow(c[0], 2) - Math.pow(c[1], 2), 2.0 * c[0] * c[1]]; -}; +const square = (c: XYType): XYType => [ + Math.pow(c[0], 2) - Math.pow(c[1], 2), + 2.0 * c[0] * c[1], +]; const sqrt = (c: XYType): XYType => { const theta = Math.atan2(c[1], c[0]); const r2 = Math.sqrt(magnitude(c)); - return [r2 * Math.cos(theta / 2), r2 * Math.sin(theta / 2)]; -}; - -const preImagePositive = function (z: XYType, c: XYType): XYType { - return sqrt(sub(z, c)); + return mult([r2, 0], [Math.cos(theta / 2), Math.sin(theta / 2)]); }; -const preImageNegative = function (z: XYType, c: XYType): XYType { - return mult([-1, 0], preImagePositive(z, c)); -}; - -const orbit = function (z: XYType, c: XYType, t: number): XYType { - for (let i = 0; i < t; i++) { +const orbit = (z: XYType, c: XYType, n: number): XYType => { + for (let i = 0; i < n; i++) { z = add(square(z), c); } return z; }; -const orbitEigenvalue = function (z: XYType, c: XYType, t: number): XYType { +const recursiveDerivative = (z: XYType, c: XYType, t: number): XYType => { let der: XYType = [1, 0]; for (let i = 0; i < t; i++) { der = mult([2, 0], mult(der, z)); @@ -75,6 +53,22 @@ const orbitEigenvalue = function (z: XYType, c: XYType, t: number): XYType { return der; }; +const cycleEigenvalue = (c: XYType, l: number, p: number): XYType => + recursiveDerivative(orbit(c, c, l), c, p); + +const magnificationRotationJulia = (c: XYType, z: XYType): XYType => { + const x = forwardOrbit([0, 0], c, 50); + const alpha = x[0][x[1]]; + let der: XYType = [1, 0]; + for (let i = 0; i < 100; i++) { + der = mult([2, 0], mult(der, z)); + z = add(square(z), c); + if (equal(alpha, z, 0.0001)) return der; + } + + return [0, 0]; +}; + /** * Subtract the first iterate in a cycle from the last. * @@ -83,9 +77,16 @@ const orbitEigenvalue = function (z: XYType, c: XYType, t: number): XYType { * @param l - The period of c * @returns The derivative of W at c */ -const W = function (c: XYType, l: number, p: number) { - const endOfCycle = orbit(c, c, l + p); +const W = (c: XYType, l: number, p: number) => { const startOfCycle = orbit(c, c, l); + const endOfCycle = orbit(startOfCycle, c, p); + + return sub(endOfCycle, startOfCycle); +}; + +const Wderivative = (c: XYType, l: number, p: number) => { + const startOfCycle = recursiveDerivative(c, c, l); + const endOfCycle = recursiveDerivative(c, c, l + p); return sub(endOfCycle, startOfCycle); }; @@ -96,17 +97,12 @@ const W = function (c: XYType, l: number, p: number) { * @param c - The point we are taking the derivative of * @returns The derivative of f at c */ -const numericalDerivative = function (c: XYType, f: (c: XYType) => XYType): XYType { - const h = 1e-9; +const numericalDerivative = (c: XYType, f: (c: XYType) => XYType): XYType => { + const epsilon = 1e-9; const withoutH = f(c); - const withH = f(add(c, [h, 0])); + const withH = f(add(c, [epsilon, 0])); - return [sub(withH, withoutH)[0] / h, sub(withH, withoutH)[1] / h]; -}; - -const cycleEigenvalue = (c: XYType, l: number, p: number): XYType => { - const firstIterateInCycle: XYType = orbit(c, c, l); - return orbitEigenvalue(firstIterateInCycle, c, p); + return [sub(withH, withoutH)[0] / epsilon, sub(withH, withoutH)[1] / epsilon]; }; /** @@ -116,13 +112,9 @@ const cycleEigenvalue = (c: XYType, l: number, p: number): XYType => { * @param p - The period of z * @returns The size of the branch */ -const magnificationRotationMandelbrot = function ( - c: XYType, - l: number, - p: number, -): XYType { +const magnificationRotationMandelbrot = (c: XYType, l: number, p: number): XYType => { const firstIterateInCycle: XYType = orbit(c, c, l); - const cycleEigenvalue: XYType = orbitEigenvalue(firstIterateInCycle, c, p); + const cycleEigenvalue: XYType = recursiveDerivative(firstIterateInCycle, c, p); return divide( numericalDerivative(c, (x: XYType) => W(x, l, p)), @@ -130,33 +122,18 @@ const magnificationRotationMandelbrot = function ( ); }; -const reachAlpha = function (c: XYType, z: XYType): number { - const alpha = getAlpha([0, 0], c); - for (let i = 0; i < 50; i++) { - if (equal(alpha, z)) return i; - z = add(square(z), c); - } - return -1; -}; - -const magnificationRotationJulia = function (c: XYType, z: XYType, q: number): XYType { - return orbitEigenvalue(z, c, reachAlpha(c, z)); -}; - -export function round(value: number, precision: number): number { - const multiplier = Math.pow(10, precision || 0); +export const round = (value: number, precision = 0): number => { + const multiplier = Math.pow(10, precision); return Math.round(value * multiplier) / multiplier; -} +}; -export function formatComplexNumber(c: XYType, precision = 3): string { - return `${round(c[0], precision)}${c[1] >= 0 ? '+' : ''}${round(c[1], precision)}i`; -} +export const formatComplexNumber = (c: XYType, precision = 3): string => + `${round(c[0], precision)}${c[1] >= 0 ? '+' : ''}${round(c[1], precision)}i`; -export function formatAngle(angle: number): string { - return `${round((180 / Math.PI) * angle, 0)}°`; -} +export const formatAngle = (angle: number): string => + `${round((180 / Math.PI) * angle, 0)}°`; -function findPotentialPreperiod(c: XYType): number { +const findPotentialPreperiod = (c: XYType): number => { let minDistance = 999999999; let minPreperiod = 0; let z: XYType = [0, 0]; @@ -173,19 +150,9 @@ function findPotentialPreperiod(c: XYType): number { } } return minPreperiod; -} - -const Wfried = function (c: XYType, l: number, p: number) { - const endOfCycle = orbitEigenvalue(c, c, l + p); - const startOfCycle = orbitEigenvalue(c, c, l); - - return sub(endOfCycle, startOfCycle); }; -export const findNearestMisiurewiczPoint = function ( - c: XYType, - iterations: number, -): XYType { +export const findNearestMisiurewiczPoint = (c: XYType, iterations: number): XYType => { const q = findPotentialPreperiod(c); if (q === 0) { return [0, 0]; @@ -194,7 +161,7 @@ export const findNearestMisiurewiczPoint = function ( const learningRate: XYType = [0.01, 0]; for (let i = 0; i < iterations; i++) { const F = W(c, q, p); - const Fdash = Wfried(c, q, p); + const Fdash = Wderivative(c, q, p); c = sub(c, mult(learningRate, divide(F, Fdash))); } return c; @@ -202,9 +169,11 @@ export const findNearestMisiurewiczPoint = function ( const depthFirstSearch = (z: XYType, c: XYType, zs: XYType[], depth: number) => { zs.push(z); - if (!equal(z, c) && depth > 0) { - depthFirstSearch(preImagePositive(z, c), c, zs, depth - 1); - depthFirstSearch(preImageNegative(z, c), c, zs, depth - 1); + if (!equal(z, c, 0.00001) && depth > 0) { + const positivePreimage = sqrt(sub(z, c)); + const negativePreimage = mult([-1, 0], positivePreimage); + depthFirstSearch(positivePreimage, c, zs, depth - 1); + depthFirstSearch(negativePreimage, c, zs, depth - 1); } return zs; }; @@ -221,30 +190,29 @@ export const similarPoints = (c: PreperiodicPoint, depth: number): PreperiodicPo return zs.map((p) => new PreperiodicPoint(c.point, p, true)); }; -const forwardOrbit = function ( +const forwardOrbit = ( z: XYType, c: XYType, maxIterations: number, -): [orbit: XYType[], prePeriod: number, period: number, flag: OrbitFlag] { +): [orbit: XYType[], prePeriod: number, period: number] => { const orbit: XYType[] = []; for (let i = 0; i < maxIterations; i++) { // eslint-disable-next-line no-loop-func orbit.push(z); const newZ = add(square(z), c); - const similar = orbit.findIndex((elem) => equal(elem, newZ, 0.001)); + const similar = orbit.findIndex((elem) => equal(elem, newZ, 0.00001)); if (similar !== -1) { - return [orbit, similar, i - similar + 1, OrbitFlag.Cyclic]; + return [orbit, similar, i - similar + 1]; } if (magnitude(z) > 2) { - return [orbit, i, -1, OrbitFlag.Divergent]; + return [orbit, i, -1]; } z = newZ; } - return [orbit, -1, -1, OrbitFlag.Acyclic]; + return [orbit, -1, -1]; }; -const subscripts = ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉']; export class PreperiodicPoint { point: XYType; prePeriod: number; @@ -259,13 +227,13 @@ export class PreperiodicPoint { constructor(c: XYType, z: XYType, julia: boolean) { this.point = z; - const orbitInfo = forwardOrbit(this.point, c, 200); + const orbitInfo = forwardOrbit(this.point, c, 50); this.prePeriod = orbitInfo[1]; this.period = orbitInfo[2]; julia - ? (this.factor = magnificationRotationJulia(c, this.point, this.prePeriod)) + ? (this.factor = magnificationRotationJulia(c, this.point)) : (this.factor = magnificationRotationMandelbrot(c, this.prePeriod, this.period)); this.factorMagnitude = magnitude(this.factor); @@ -280,33 +248,10 @@ export class PreperiodicPoint { } toString(): string { - let pre = `M${this.prePeriod},${this.period}`; - for (let i = 0; i < 10; i++) { - pre = pre.replaceAll(i.toString(), subscripts[i]); - } - return pre; + return formatComplexNumber(this.point); } } -/** - * the point where zero enters a cycle - * - * @param c - The point - * @returns If it's preperiodic: the preperiod, if it's periodic: nonsense, otherwise: -1. - */ -const getAlpha = (z: XYType, c: XYType): XYType => { - const olds: XYType[] = []; - for (let i = 0; i < 50; i++) { - olds.push(z); - const newZ = add(square(z), c); - const similar = olds.findIndex((elem) => equal(elem, newZ)); - if (similar !== -1) return newZ; - - z = newZ; - } - return [-7, -7]; -}; - export const alignSets = ( newMagnification: number, mandelbrotControls: ViewerControlSprings,