├── .eslintrc.js ├── .gitignore ├── .prettierignore ├── .prettierrc ├── LICENSE ├── README.md ├── gatsby-browser.js ├── gatsby-config.js ├── gatsby-node.js ├── gatsby-ssr.js ├── package-lock.json ├── package.json ├── src ├── components │ ├── AlgorithmModals.jsx │ ├── CoordinateInput.jsx │ ├── InformationModal.jsx │ ├── IntroductionModal.jsx │ ├── Layout.jsx │ ├── MapPlot.jsx │ ├── Menu.jsx │ ├── MenuHeader.jsx │ ├── MenuItem.jsx │ ├── MenuMetrics.jsx │ ├── MenuPointControls.jsx │ ├── MenuSection.jsx │ ├── MenuSolverControls.jsx │ ├── OtherControls.jsx │ ├── SEO.jsx │ ├── ThemeToggle.js │ └── index.js ├── constants.js ├── content │ ├── exhaustive │ │ ├── branchAndBoundOnCost.md │ │ ├── branchAndBoundOnCostAndCross.md │ │ ├── depthFirstSearch.md │ │ └── random.md │ ├── heurisitc-construction │ │ ├── arbitraryInsertion.md │ │ ├── convexHull.md │ │ ├── furthestInsertion.md │ │ ├── nearestInsertion.md │ │ ├── nearestNeighbor.md │ │ └── simulatedAnnealing.md │ ├── heuristic-improvement │ │ ├── twoOptInversion.md │ │ └── twoOptReciprocalExchange.md │ └── introduction.md ├── context │ ├── PreSetTheme.jsx │ ├── ThemeContext.jsx │ └── index.js ├── hooks │ ├── index.js │ ├── useAlgorithmInfo.js │ ├── useIntroductionInfo.js │ ├── useIsFirstLoad.js │ ├── usePersistentState.js │ ├── useSolverWorker.js │ └── useUpdateEffect.js ├── images │ └── favicon.png ├── pages │ └── index.js ├── solvers │ ├── cost.js │ ├── exhaustive │ │ ├── branchAndBoundOnCost.worker.js │ │ ├── branchAndBoundOnCostAndCross.worker.js │ │ ├── depthFirstSearch.worker.js │ │ └── random.worker.js │ ├── heuristic-construction │ │ ├── arbitraryInsertion.worker.js │ │ ├── convexHull.worker.js │ │ ├── furthestInsertion.worker.js │ │ ├── nearestInsertion.worker.js │ │ ├── nearestNeighbor.worker.js │ │ └── simulatedAnnealing.worker.js │ ├── heuristic-improvement │ │ ├── twoOptInversion.worker.js │ │ └── twoOptReciprocalExchange.worker.js │ ├── index.js │ └── makeSolver.js └── store │ ├── actions.js │ ├── emitCustomEvent.js │ ├── reducer.js │ ├── selectors.js │ └── store.js └── static ├── .well-known └── brave-rewards-verification.txt ├── CNAME └── robots.txt /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | env: { 3 | browser: true, 4 | es6: true, 5 | node: true 6 | }, 7 | extends: "react-app", 8 | globals: { 9 | Atomics: "readonly", 10 | SharedArrayBuffer: "readonly", 11 | __PATH_PREFIX__: true 12 | }, 13 | parserOptions: { 14 | ecmaFeatures: { 15 | jsx: true 16 | }, 17 | ecmaVersion: 2018, 18 | sourceType: "module" 19 | }, 20 | plugins: ["react"] 21 | }; 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # dotenv environment variables file 55 | .env 56 | 57 | # gatsby files 58 | .cache/ 59 | public 60 | 61 | # Mac files 62 | .DS_Store 63 | 64 | # Yarn 65 | yarn-error.log 66 | .pnp/ 67 | .pnp.js 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | 72 | .vscode/ 73 | 74 | 75 | package-lock.json -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | .cache 2 | package.json 3 | package-lock.json 4 | public 5 | .vscode/ -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "endOfLine": "lf", 3 | "semi": true, 4 | "singleQuote": false, 5 | "tabWidth": 2 6 | } 7 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jeff Hackshaw 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Netlify Status](https://api.netlify.com/api/v1/badges/e21365cc-96a9-4649-9ad2-a35bb42d4a9f/deploy-status)](https://app.netlify.com/sites/tspvis/deploys) 2 | [![code style: prettier](https://img.shields.io/badge/code_style-prettier-ff69b4.svg)](https://github.com/prettier/prettier) 3 | [![Live Demo](https://img.shields.io/badge/demo-online-green.svg)](https://tspvis.com) 4 | [![Contributors](https://img.shields.io/github/contributors/jhackshaw/tspvis)](https://github.com/jhackshaw/tspvis/graphs/contributors) 5 | ![GitHub](https://img.shields.io/github/license/jhackshaw/tspvis) 6 | 7 | 8 | > [!WARNING] 9 | > **Seeking maintainers**: If you're qualified and interested in taking over this project, I'd love to hear from you! The analytics suggest it is still helpful to a number of users, but I don't currently have the time to give it the attention it deserves. There's a lot of potential here and I'm looking to transition this to someone interested in making it better. Please shoot me a message if you're interested. 10 | 11 | 12 | # Traveling Salesman Problem 13 | 14 | ![](https://media.giphy.com/media/LPkQ56C9z0iSv9Hs9D/giphy.gif) 15 | 16 | The traveling salesman problem (TSP) asks the question, "Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city and returns to the origin city?". 17 | 18 | ### This project 19 | 20 | - Live at [tspvis.com](https://tspvis.com) 21 | - The goal of this site is to be an **educational** resource to help visualize, learn, and develop different algorithms for the traveling salesman problem in a way that's easily accessible 22 | - As you apply different algorithms, the current best path is saved and used as input to whatever you run next. The order in which you apply different algorithms to the problem is sometimes referred to the meta-heuristic strategy. 23 | - For example, apply nearest neighbor, then 2-opt inversion, then branch and bound 24 | 25 | ### Heuristic algorithms 26 | 27 | Heuristic algorithms attempt to find a good approximation of the optimal path within a more _reasonable_ amount of time. 28 | 29 | **Construction** - Build a path 30 | 31 | - Nearest Neighbor 32 | - Arbitrary Insertion 33 | - Furthest Insertion 34 | - Nearest Insertion 35 | - Convex Hull Insertion\* 36 | - Simulated Annealing\* 37 | 38 | **Improvement** - Attempt to take an existing constructed path and improve on it 39 | 40 | - 2-Opt Reciprocal Exchange 41 | - 2-Opt Inversion\* 42 | 43 | ### Exhaustive algorithms 44 | 45 | Exhaustive algorithms will always find the best possible solution by evaluating every possible path. These algorithms are typically significantly more expensive then the heuristic algorithms discussed above. The exhaustive algorithms implemented so far include: 46 | 47 | - Random Paths 48 | - Depth First Search (Brute Force) 49 | - Branch and Bound (Cost) 50 | - Branch and Bound (Cost, Intersections)\* 51 | 52 | ## Dependencies 53 | 54 | These are the main tools used to build this site: 55 | 56 | - [gatsbyjs](https://www.gatsbyjs.org) 57 | - [reactjs](https://reactjs.org) 58 | - [web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) 59 | - [material-ui](https://material-ui.com/) 60 | - [deck.gl](https://deck.gl/#/) 61 | - [mapbox](https://www.mapbox.com/) 62 | 63 | ## Contributing 64 | 65 | Pull requests are always welcome! Also, feel free to raise any ideas, suggestions, or bugs as an issue. 66 | 67 | ## Instructions on How to Install 68 | 69 | To set up the project locally, follow these steps: 70 | 71 | 1. **Clone the repository**: 72 | ```bash 73 | git clone https://github.com/jhackshaw/tspvis.git 74 | cd tspvis 75 | ``` 76 | 77 | 2. **Install libvips**: 78 | On macOS, you can install libvips using Homebrew: 79 | ```bash 80 | brew install vips 81 | ``` 82 | 83 | 3. **Install dependencies**: 84 | Ensure you have [Node.js](https://nodejs.org/) installed. Then, run: 85 | ```bash 86 | npm install 87 | ``` 88 | 89 | 4. **Start the development server**: 90 | To run the project locally, use: 91 | ```bash 92 | npm run develop 93 | ``` 94 | This will start a local development server, typically accessible at `http://localhost:8000`. 95 | 96 | 5. **Build for production**: 97 | If you want to create a production build, run: 98 | ```bash 99 | npm run build 100 | ``` 101 | The output will be in the `public/` directory. 102 | 103 | 6. **Serve the production build**: 104 | To test the production build locally, use: 105 | ```bash 106 | npm run serve 107 | ``` 108 | 109 | 7. **Clean the cache**: 110 | If you encounter issues, you can clean the Gatsby cache and public directories: 111 | ```bash 112 | npm run clean 113 | ``` 114 | -------------------------------------------------------------------------------- /gatsby-browser.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Provider } from "react-redux"; 3 | import store from "./src/store/store"; 4 | import "react-vis/dist/style.css"; 5 | import "prismjs/themes/prism-tomorrow.css"; 6 | import "typeface-roboto"; 7 | import { ThemeContextProvider } from "./src/context"; 8 | 9 | export const wrapRootElement = ({ element }) => ( 10 | 11 | {element} 12 | 13 | ); 14 | 15 | export const onServiceWorkerUpdateReady = () => { 16 | window.location.reload(); 17 | }; 18 | -------------------------------------------------------------------------------- /gatsby-config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | siteMetadata: { 3 | title: `Traveling Salesman Problem Solver`, 4 | description: ``, 5 | author: `@jhackshaw` 6 | }, 7 | plugins: [ 8 | `gatsby-plugin-material-ui`, 9 | `gatsby-plugin-react-helmet`, 10 | { 11 | resolve: "gatsby-plugin-google-tagmanager", 12 | options: { 13 | id: "GTM-PS28HLQ", 14 | includeInDevelopment: false 15 | } 16 | }, 17 | { 18 | resolve: `gatsby-source-filesystem`, 19 | options: { 20 | name: "content", 21 | path: `${__dirname}/src/content` 22 | } 23 | }, 24 | { 25 | resolve: `gatsby-transformer-remark`, 26 | options: { 27 | plugins: [`gatsby-remark-prismjs`] 28 | } 29 | }, 30 | { 31 | resolve: `gatsby-plugin-manifest`, 32 | options: { 33 | name: `tsp visualizer`, 34 | short_name: `tspvis`, 35 | start_url: `/`, 36 | background_color: `#663399`, 37 | theme_color: `#663399`, 38 | display: `fullscreen`, 39 | icon: `src/images/favicon.png` 40 | } 41 | }, 42 | `gatsby-plugin-offline` 43 | ] 44 | }; 45 | -------------------------------------------------------------------------------- /gatsby-node.js: -------------------------------------------------------------------------------- 1 | exports.onCreateWebpackConfig = ({ 2 | actions: { replaceWebpackConfig }, 3 | getConfig 4 | }) => { 5 | const config = getConfig(); 6 | 7 | config.module.rules.push({ 8 | test: /\.worker\.js$/, 9 | use: { 10 | loader: "worker-loader", 11 | options: { 12 | inline: true 13 | } 14 | } 15 | }); 16 | 17 | config.output.globalObject = "this"; 18 | 19 | replaceWebpackConfig(config); 20 | }; 21 | -------------------------------------------------------------------------------- /gatsby-ssr.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { ThemeContextProvider, PreSetTheme } from "./src/context"; 3 | import { Provider } from "react-redux"; 4 | import store from "./src/store/store"; 5 | import "react-vis/dist/style.css"; 6 | 7 | export const wrapRootElement = ({ element }) => { 8 | return ( 9 | 10 | {element} 11 | 12 | ); 13 | }; 14 | 15 | export const onRenderBody = ({ setPreBodyComponents }) => { 16 | setPreBodyComponents([]); 17 | }; 18 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tsp-visualizer", 3 | "private": true, 4 | "description": "Visualizer for the traveling salesman problem", 5 | "version": "0.1.0", 6 | "dependencies": { 7 | "@fortawesome/fontawesome-svg-core": "^1.2.35", 8 | "@fortawesome/free-brands-svg-icons": "^5.15.3", 9 | "@fortawesome/free-solid-svg-icons": "^5.15.3", 10 | "@fortawesome/react-fontawesome": "^0.1.14", 11 | "@material-ui/core": "^4.11.3", 12 | "@material-ui/styles": "^4.11.3", 13 | "babel-plugin-lodash": "^3.3.4", 14 | "deck.gl": "^7.3.15", 15 | "eslint-config-react-app": "^5.2.1", 16 | "gatsby": "^2.32.12", 17 | "gatsby-cli": "^2.19.2", 18 | "gatsby-plugin-google-tagmanager": "^2.11.0", 19 | "gatsby-plugin-manifest": "^2.12.1", 20 | "gatsby-plugin-material-ui": "^2.1.6", 21 | "gatsby-plugin-offline": "^3.10.2", 22 | "gatsby-plugin-react-helmet": "^3.10.0", 23 | "gatsby-remark-prismjs": "^3.13.0", 24 | "gatsby-source-filesystem": "^2.11.1", 25 | "gatsby-transformer-remark": "^2.16.1", 26 | "gh-pages": "^2.2.0", 27 | "prismjs": "^1.23.0", 28 | "react": "^16.13.1", 29 | "react-dom": "^16.13.1", 30 | "react-helmet": "^5.2.1", 31 | "react-map-gl": "^5.3.12", 32 | "react-redux": "^7.2.3", 33 | "react-vis": "^1.11.7", 34 | "redux": "^4.0.5", 35 | "redux-thunk": "^2.3.0", 36 | "reselect": "^4.0.0", 37 | "typeface-roboto": "0.0.75", 38 | "worker-loader": "^2.0.0" 39 | }, 40 | "devDependencies": { 41 | "eslint": "^6.8.0", 42 | "eslint-plugin-react": "^7.23.2", 43 | "prettier": "^1.19.1" 44 | }, 45 | "keywords": [ 46 | "gatsby" 47 | ], 48 | "license": "MIT", 49 | "scripts": { 50 | "build": "gatsby build", 51 | "develop": "gatsby develop", 52 | "format": "prettier --write \"**/*.{js,jsx,json,md}\"", 53 | "start": "npm run develop", 54 | "serve": "gatsby serve", 55 | "clean": "gatsby clean" 56 | }, 57 | "repository": { 58 | "type": "git", 59 | "url": "https://github.com/jhackshaw/tspvis" 60 | }, 61 | "bugs": { 62 | "url": "https://github.com/jhackshaw/tspvis/issues" 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/components/AlgorithmModals.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | 4 | import { useAlgorithmInfo } from "../hooks"; 5 | import * as selectors from "../store/selectors"; 6 | import * as actions from "../store/actions"; 7 | 8 | import { InformationModal } from "./InformationModal"; 9 | 10 | export const AlgorithmModals = props => { 11 | const dispatch = useDispatch(); 12 | const algorithms = useAlgorithmInfo(); 13 | const selectedAlgorithm = useSelector(selectors.selectAlgorithm); 14 | const open = useSelector(selectors.selectAlgInfoOpen); 15 | 16 | const onClose = () => { 17 | dispatch(actions.toggleAlgInfoOpen()); 18 | }; 19 | 20 | return ( 21 | <> 22 | {algorithms.map(alg => ( 23 | 28 |
29 | 30 | ))} 31 | 32 | ); 33 | }; 34 | -------------------------------------------------------------------------------- /src/components/CoordinateInput.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from 'react'; 2 | import {ButtonGroup, Button, TextField} from "@material-ui/core"; 3 | import { useDispatch, useSelector } from 'react-redux'; 4 | import { updatePoints } from '../store/actions'; 5 | import * as selectors from '../store/selectors'; 6 | import * as actions from '../store/actions'; 7 | 8 | const CoordinateInput = () => { 9 | const [coordinates, setCoordinates] = useState(''); 10 | const dispatch = useDispatch(); 11 | const viewport = useSelector(selectors.selectViewport); 12 | 13 | const handleUpdate = () => { 14 | console.log('Raw coordinates input:', coordinates); 15 | 16 | const parsedCoordinates = coordinates 17 | .split('\n') 18 | .map(line => { 19 | const [lat, lng] = line.split(',').map(coord => parseFloat(coord.trim())); 20 | if ( 21 | isNaN(lat) || 22 | isNaN(lng) || 23 | lat < -90 || 24 | lat > 90 || 25 | lng < -180 || 26 | lng > 180 27 | ) { 28 | console.error(`Invalid coordinate: ${line}`); 29 | return null; 30 | } 31 | return [lat, lng]; 32 | }) 33 | .filter(coord => coord !== null); 34 | 35 | console.log('Parsed coordinates:', parsedCoordinates); 36 | 37 | if (parsedCoordinates.length > 0) { 38 | dispatch(updatePoints(parsedCoordinates)); 39 | const [firstLng, firstLat] = parsedCoordinates[0]; 40 | console.log('Updating viewport to:', { 41 | latitude: firstLat, 42 | longitude: firstLng, 43 | zoom: 10 44 | }); 45 | dispatch( 46 | actions.setViewportState({ 47 | ...viewport, 48 | latitude: firstLat, 49 | longitude: firstLng, 50 | zoom: 10 51 | }) 52 | ); 53 | } else { 54 | console.error('No valid coordinates provided.'); 55 | } 56 | }; 57 | 58 | return ( 59 | <> 60 | setCoordinates(e.target.value)} 64 | multiline 65 | rows={10} 66 | fullWidth 67 | variant="outlined" 68 | color="secondary" 69 | /> 70 |
71 | 77 | 82 | 83 | 84 | ); 85 | }; 86 | 87 | export default CoordinateInput; -------------------------------------------------------------------------------- /src/components/InformationModal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/styles"; 3 | import { 4 | Dialog, 5 | DialogContent, 6 | IconButton, 7 | useMediaQuery, 8 | useTheme 9 | } from "@material-ui/core"; 10 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 11 | import { faWindowClose } from "@fortawesome/free-solid-svg-icons"; 12 | 13 | const useStyles = makeStyles(theme => ({ 14 | closeButton: { 15 | position: "absolute", 16 | right: theme.spacing(1), 17 | top: theme.spacing(1), 18 | color: theme.palette.grey[500] 19 | }, 20 | root: { 21 | zIndex: "10000 !important" 22 | } 23 | })); 24 | 25 | export const InformationModal = ({ open, onClose, children }) => { 26 | const classes = useStyles(); 27 | const theme = useTheme(); 28 | const fullScreen = useMediaQuery(theme.breakpoints.down("sm")); 29 | 30 | return ( 31 | 42 | 43 | 48 | 49 | 50 | 51 | {children} 52 | 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /src/components/IntroductionModal.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | 4 | import * as selectors from "../store/selectors"; 5 | import * as actions from "../store/actions"; 6 | import { useIntroductionInfo } from "../hooks"; 7 | 8 | import { InformationModal } from "./InformationModal"; 9 | 10 | export const IntroductionModal = props => { 11 | const dispatch = useDispatch(); 12 | const introduction = useIntroductionInfo(); 13 | const open = useSelector(selectors.selectSiteInfoOpen); 14 | 15 | const onClose = () => { 16 | dispatch(actions.toggleSiteInfoOpen()); 17 | }; 18 | 19 | return ( 20 | 21 |
22 | 23 | ); 24 | }; 25 | -------------------------------------------------------------------------------- /src/components/Layout.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/styles"; 3 | 4 | const useStyles = makeStyles(theme => ({ 5 | container: { 6 | height: "100vh", 7 | display: "flex", 8 | overflow: "hidden", 9 | flexDirection: "row" 10 | }, 11 | [theme.breakpoints.down("sm")]: { 12 | container: { 13 | justifyContent: "flex-end", 14 | flexDirection: "column-reverse" 15 | } 16 | } 17 | })); 18 | 19 | export const Layout = ({ children }) => { 20 | const classes = useStyles(); 21 | 22 | return
{children}
; 23 | }; 24 | -------------------------------------------------------------------------------- /src/components/MapPlot.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useImperativeHandle, useEffect, useMemo } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { useMediaQuery } from "@material-ui/core"; 4 | import MapGL from "react-map-gl"; 5 | import "mapbox-gl/dist/mapbox-gl.css"; 6 | import DeckGL from '@deck.gl/react'; 7 | import { ScatterplotLayer, PathLayer } from "@deck.gl/layers"; 8 | import { LinearProgress } from "@material-ui/core"; 9 | import * as actions from "../store/actions"; 10 | import * as selectors from "../store/selectors"; 11 | import { useThemeContext } from "../context"; 12 | 13 | // not secret 14 | const TOKEN = 15 | "pk.eyJ1IjoiaW50cmVwaWRldiIsImEiOiJjazBpa2M5YnowMHcyM21ubzgycW8zZHJmIn0.DCO2aRA6MJweC8HN-d_cgQ"; 16 | 17 | export const MapPlot = React.forwardRef((props, ref) => { 18 | const { children } = props; 19 | const { muiTheme, colorMode } = useThemeContext(); 20 | const matches = useMediaQuery(muiTheme.breakpoints.down("sm")); 21 | const mapGlRef = useRef(); 22 | const plotPoints = useSelector(selectors.selectPointsDisplay); 23 | const plotPaths = useSelector(selectors.selectPlotPaths); 24 | const viewport = useSelector(selectors.selectViewport); 25 | const running = useSelector(selectors.selectRunning); 26 | const definingPoints = useSelector(selectors.selectDefiningPoints); 27 | const mapStyle = useMemo(() => 28 | colorMode === "dark" 29 | ? "mapbox://styles/mapbox/dark-v8" 30 | : "mapbox://styles/mapbox/light-v8" 31 | ); 32 | const dispatch = useDispatch(); 33 | 34 | useImperativeHandle(ref, () => ({ 35 | getBounds: () => { 36 | const map = mapGlRef.current.getMap(); 37 | const { _ne, _sw } = map.getBounds(); 38 | return { 39 | top: _ne.lat, 40 | bottom: _sw.lat, 41 | left: _ne.lng, 42 | right: _sw.lng 43 | }; 44 | } 45 | })); 46 | 47 | useEffect(() => { 48 | if (matches) { 49 | dispatch( 50 | actions.setViewportState({ 51 | ...viewport, 52 | zoom: 2 53 | }) 54 | ); 55 | } 56 | }, [matches, dispatch]); // eslint-disable-line react-hooks/exhaustive-deps 57 | 58 | useEffect(() => { 59 | if (!viewport.latitude || !viewport.longitude) { 60 | dispatch(actions.setViewportState(initialViewport)); 61 | } 62 | }, [viewport, dispatch]); 63 | 64 | const onViewportChanged = viewport => { 65 | dispatch(actions.setViewportState(viewport)); 66 | }; 67 | 68 | const onDefinedPoint = ({ lngLat }) => { 69 | dispatch(actions.addDefinedPoint(lngLat)); 70 | }; 71 | const layers = useMemo(() => [ 72 | new PathLayer({ 73 | id: "path-layer", 74 | data: plotPaths, 75 | getPath: d => d.path, 76 | getColor: d => d.color, 77 | pickable: true, 78 | widthMinPixels: 4, 79 | widthMaxPixels: 8 80 | }), 81 | new ScatterplotLayer({ 82 | id: "scatter-layer", 83 | data: plotPoints, 84 | pickable: true, 85 | opacity: 0.8, 86 | getFillColor: p => p.color, 87 | radiusMinPixels: 6, 88 | radiusMaxPixels: 8 89 | }) 90 | ], [plotPaths, plotPoints]); 91 | 92 | return ( 93 | 94 | 107 | {running && } 108 | 109 | 110 | {children} 111 | 112 | ); 113 | }); 114 | -------------------------------------------------------------------------------- /src/components/Menu.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/styles"; 3 | import { Paper, Divider } from "@material-ui/core"; 4 | import { MenuHeader } from "./MenuHeader"; 5 | import { MenuSolverControls } from "./MenuSolverControls"; 6 | import { MenuMetrics } from "./MenuMetrics"; 7 | import { MenuPointControls } from "./MenuPointControls"; 8 | import { OtherControls } from "./OtherControls"; 9 | 10 | const useStyles = makeStyles(theme => ({ 11 | wrapper: { 12 | overflowY: "auto", 13 | flex: "0 0 400px", 14 | padding: theme.spacing(2), 15 | display: "flex", 16 | flexDirection: "column", 17 | flexWrap: "nowrap", 18 | zIndex: 100 19 | }, 20 | [theme.breakpoints.down("sm")]: { 21 | width: "100%" 22 | } 23 | })); 24 | 25 | export const Menu = ({ 26 | onStart, 27 | onPause, 28 | onUnPause, 29 | onFullSpeed, 30 | onStop, 31 | onStep, 32 | onRandomizePoints 33 | }) => { 34 | const classes = useStyles(); 35 | 36 | return ( 37 | 38 | 39 | 40 | 41 | 49 | 50 | 51 | 52 | 53 | 54 | ); 55 | }; 56 | -------------------------------------------------------------------------------- /src/components/MenuHeader.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import { Grid, Typography, IconButton, Tooltip } from "@material-ui/core"; 4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 5 | import { 6 | faInfoCircle, 7 | faBriefcase, 8 | faDonate 9 | } from "@fortawesome/free-solid-svg-icons"; 10 | import { faGithub } from "@fortawesome/free-brands-svg-icons"; 11 | import { makeStyles } from "@material-ui/styles"; 12 | import { MenuSection } from "./MenuSection"; 13 | 14 | import * as actions from "../store/actions"; 15 | 16 | const useStyles = makeStyles(theme => ({ 17 | root: { 18 | paddingTop: theme.spacing(3), 19 | paddingBottom: theme.spacing(3) 20 | }, 21 | title: { 22 | fontSize: "1.2rem" 23 | } 24 | })); 25 | 26 | export const MenuHeader = props => { 27 | const classes = useStyles(); 28 | const dispatch = useDispatch(); 29 | 30 | const onOpenSiteInfo = () => { 31 | dispatch(actions.toggleSiteInfoOpen()); 32 | }; 33 | 34 | return ( 35 | 36 | 37 | 44 | TSPVIS 45 | 46 | 47 | 48 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | Visualize algorithms for the traveling salesman problem. Use the 65 | controls below to plot points, choose an algorithm, and control 66 | execution. 67 |
68 | (Hint: try a construction algorithm followed by an improvement 69 | algorithm) 70 |
71 |
72 | ); 73 | }; 74 | -------------------------------------------------------------------------------- /src/components/MenuItem.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/styles"; 3 | import { Grid, Typography } from "@material-ui/core"; 4 | 5 | const useStyles = makeStyles(theme => ({ 6 | item: { 7 | margin: `${theme.spacing(1.5)}px 0` 8 | } 9 | })); 10 | 11 | export const MenuItem = ({ children, title = "", row = false }) => { 12 | const classes = useStyles(); 13 | 14 | return ( 15 |
16 | 22 | {title && ( 23 | 24 | 30 | {title} 31 | 32 | 33 | )} 34 | {children} 35 | 36 |
37 | ); 38 | }; 39 | -------------------------------------------------------------------------------- /src/components/MenuMetrics.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { Grid, Typography } from "@material-ui/core"; 4 | import * as selectors from "../store/selectors"; 5 | 6 | import { MenuSection } from "./MenuSection"; 7 | import { MenuItem } from "./MenuItem"; 8 | import { makeStyles } from "@material-ui/styles"; 9 | 10 | const useStyles = makeStyles(theme => ({ 11 | grow: { 12 | flexGrow: 1, 13 | paddingRight: theme.spacing(1) 14 | }, 15 | unit: { 16 | flexShrink: 0, 17 | width: "2rem" 18 | } 19 | })); 20 | 21 | export const MenuMetrics = props => { 22 | const classes = useStyles(); 23 | const best = useSelector(selectors.selectBestCostDisplay); 24 | const evaluating = useSelector(selectors.selectEvaluatingCostDisplay); 25 | const startedRunningAt = useSelector(selectors.selectStartedRunningAt); 26 | const [runningFor, setRunningFor] = useState(0); 27 | 28 | useEffect(() => { 29 | if (startedRunningAt) { 30 | const interval = setInterval(() => { 31 | const elapsed = Date.now() - startedRunningAt; 32 | const seconds = Math.floor(elapsed / 1000); 33 | const milliseconds = Math.floor((elapsed % 1000) / 10); // Get two decimal places 34 | setRunningFor(`${seconds}.${milliseconds.toString().padStart(2, '0')}`); 35 | }, 100); 36 | return () => clearInterval(interval); 37 | } 38 | }, [startedRunningAt]); 39 | 40 | return ( 41 | 42 | 43 | 44 | 50 | Current Best:{" "} 51 | 52 | 58 | {best} 59 | 60 | 66 | km 67 | 68 | 69 | 70 | 76 | Evaluating:{" "} 77 | 78 | 84 | {evaluating} 85 | 86 | 92 | km 93 | 94 | 95 | 96 | 97 | 103 | Running For:{" "} 104 | 105 | 111 | {runningFor || ""} 112 | 113 | 119 | s 120 | 121 | 122 | 123 | 124 | ); 125 | }; 126 | -------------------------------------------------------------------------------- /src/components/MenuPointControls.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState, useEffect } from "react"; 2 | import { useSelector, useDispatch } from "react-redux"; 3 | import { 4 | ButtonGroup, 5 | Button, 6 | Slider, 7 | Grid, 8 | Typography, 9 | makeStyles 10 | } from "@material-ui/core"; 11 | import { 12 | faRandom, 13 | faSave, 14 | faMousePointer, 15 | faMapMarked 16 | } from "@fortawesome/free-solid-svg-icons"; 17 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 18 | 19 | import { MenuSection } from "./MenuSection"; 20 | import { MenuItem } from "./MenuItem"; 21 | import * as selectors from "../store/selectors"; 22 | import * as actions from "../store/actions"; 23 | 24 | const useStyles = makeStyles(theme => ({ 25 | grow: { 26 | flexGrow: 1 27 | } 28 | })); 29 | 30 | // [0, 1/2, 1, 3, 12] 31 | let cache = ["1e+0", "1e+0"]; 32 | const possRoutes = n => { 33 | if (n <= 2) { 34 | return "1e+0"; 35 | } 36 | if (typeof cache[n - 1] !== "undefined") { 37 | return cache[n - 1]; 38 | } 39 | 40 | let result = 1; 41 | 42 | for (let i = 1; i <= n; i++) { 43 | result *= i; 44 | cache[i] = (result / 2).toExponential(3); 45 | } 46 | 47 | return cache[n - 1]; 48 | }; 49 | 50 | export const MenuPointControls = ({ onRandomizePoints }) => { 51 | const classes = useStyles(); 52 | const [possiblePaths, setPossiblePaths] = useState("0"); 53 | const dispatch = useDispatch(); 54 | const pointCount = useSelector(selectors.selectPointCount); 55 | const running = useSelector(selectors.selectRunning); 56 | const definingPoints = useSelector(selectors.selectDefiningPoints); 57 | 58 | const onDefaultMap = () => { 59 | dispatch(actions.setDefaultMap()); 60 | }; 61 | 62 | const onToggleDefiningPoints = () => { 63 | const action = definingPoints 64 | ? actions.stopDefiningPoints() 65 | : actions.startDefiningPoints(); 66 | dispatch(action); 67 | }; 68 | 69 | const onPointCountChange = (_, newCount) => { 70 | dispatch(actions.setPointCount(newCount)); 71 | }; 72 | 73 | useEffect(() => { 74 | setPossiblePaths(possRoutes(pointCount)); 75 | }, [pointCount]); 76 | 77 | const [num, exp] = possiblePaths.split("e+"); 78 | 79 | return ( 80 | 81 | 82 | 89 | 95 | 101 | 104 | 105 | 106 | 107 | 108 | 118 | 119 | 120 | 121 | 127 | Possible Paths:{" "} 128 | 129 | 135 | {num} x 10{exp} 136 | 137 | 138 | 139 | 140 | ); 141 | }; 142 | -------------------------------------------------------------------------------- /src/components/MenuSection.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { makeStyles } from "@material-ui/styles"; 3 | import { Grid } from "@material-ui/core"; 4 | 5 | const useStyles = makeStyles(theme => ({ 6 | section: { 7 | padding: theme.spacing(2), 8 | // backgroundColor: ({ highlight = false }) => 9 | // highlight ? theme.palette.grey[100] : theme.palette.paper, 10 | border: ({ highlight = false }) => 11 | highlight ? `2px solid ${theme.palette.secondary.main}` : "none", 12 | borderRadius: "10px" 13 | } 14 | })); 15 | 16 | export const MenuSection = ({ children, ...rest }) => { 17 | const classes = useStyles(rest); 18 | 19 | return ( 20 |
21 | 22 | {children} 23 | 24 |
25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/components/MenuSolverControls.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | ButtonGroup, 4 | Button, 5 | Slider, 6 | Select, 7 | ListSubheader, 8 | MenuItem as SelectItem, 9 | Typography, 10 | Switch, 11 | Grid, 12 | IconButton 13 | } from "@material-ui/core"; 14 | import { useDispatch, useSelector } from "react-redux"; 15 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"; 16 | import { 17 | faPlay, 18 | faStop, 19 | faRedo, 20 | faQuestion, 21 | faFastForward, 22 | faPause, 23 | faStepForward 24 | } from "@fortawesome/free-solid-svg-icons"; 25 | import { MenuSection } from "./MenuSection"; 26 | import { MenuItem } from "./MenuItem"; 27 | import { useAlgorithmInfo } from "../hooks"; 28 | import * as actions from "../store/actions"; 29 | import * as selectors from "../store/selectors"; 30 | import CoordinateInput from './CoordinateInput'; 31 | 32 | export const MenuSolverControls = ({ 33 | onStart, 34 | onPause, 35 | onUnPause, 36 | onFullSpeed, 37 | onStop, 38 | onStep 39 | }) => { 40 | const dispatch = useDispatch(); 41 | const algorithms = useAlgorithmInfo(); 42 | const selectedAlgorithm = useSelector(selectors.selectAlgorithm); 43 | const delay = useSelector(selectors.selectDelay); 44 | const evaluatingDetailLevel = useSelector( 45 | selectors.selectEvaluatingDetailLevel 46 | ); 47 | const maxEvaluatingDetailLevel = useSelector( 48 | selectors.selectMaxEvaluatingDetailLevel 49 | ); 50 | const showBestPath = useSelector(selectors.selectShowBestPath); 51 | const running = useSelector(selectors.selectRunning); 52 | const fullSpeed = useSelector(selectors.selectFullSpeed); 53 | const paused = useSelector(selectors.selectPaused); 54 | const definingPoints = useSelector(selectors.selectDefiningPoints); 55 | 56 | const onAlgorithmChange = event => { 57 | event.persist(); 58 | onStop(); 59 | const solverKey = event.target.value; 60 | const { defaults } = algorithms.find(alg => alg.solverKey === solverKey); 61 | dispatch(actions.setAlgorithm(solverKey, defaults)); 62 | }; 63 | 64 | const onDelayChange = (_, newDelay) => { 65 | dispatch(actions.setDelay(newDelay)); 66 | }; 67 | 68 | const onEvaluatingDetailLevelChange = (onLevel, offLevel) => event => { 69 | const level = event.target.checked ? onLevel : offLevel; 70 | dispatch(actions.setEvaluatingDetailLevel(level)); 71 | }; 72 | 73 | const onShowBestPathChange = event => { 74 | dispatch(actions.setShowBestPath(event.target.checked)); 75 | }; 76 | 77 | const onReset = () => { 78 | onStop(); 79 | dispatch(actions.resetSolverState()); 80 | }; 81 | 82 | const onShowAlgInfo = () => { 83 | dispatch(actions.toggleAlgInfoOpen()); 84 | }; 85 | 86 | return ( 87 | <> 88 | 89 | 90 | 91 | 92 | 125 | 126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 144 | 150 | 156 | 159 | 160 | 161 | 162 | 163 | 173 | 174 | 175 | 176 | 177 | 178 | 179 | Show Best Path 180 | 181 | 182 | 183 | 190 | 191 | 192 | 193 | 194 | Show Evaluated Paths 195 | 196 | 197 | 198 | 0} 200 | onChange={onEvaluatingDetailLevelChange(1, 0)} 201 | color="secondary" 202 | disabled={definingPoints || fullSpeed} 203 | id="show-evaluating-paths" 204 | /> 205 | 206 | 207 | {maxEvaluatingDetailLevel > 1 && ( 208 | <> 209 | 210 | 215 | Show Evaluated Steps 216 | 217 | 218 | 219 | 1} 221 | onChange={onEvaluatingDetailLevelChange(2, 1)} 222 | color="secondary" 223 | disabled={definingPoints || fullSpeed} 224 | id="show-evaluating-steps" 225 | /> 226 | 227 | 228 | )} 229 | 230 | 231 | 232 | 233 | 238 | Add custom list of coordinates 239 | 240 | 241 | 242 | 243 | 244 | ); 245 | }; 246 | -------------------------------------------------------------------------------- /src/components/OtherControls.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Grid, Typography } from "@material-ui/core"; 3 | import { MenuItem } from "./MenuItem"; 4 | import { MenuSection } from "./MenuSection"; 5 | import { ThemeToggle } from "./ThemeToggle"; 6 | 7 | export const OtherControls = props => { 8 | return ( 9 | 10 | 11 | 12 | 13 | Dark Mode 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | ); 22 | }; 23 | -------------------------------------------------------------------------------- /src/components/SEO.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Helmet from "react-helmet"; 3 | 4 | const description = 5 | "Interactive solver for the traveling salesman problem to visualize different algorithms. Includes various Heuristic and Exhaustive algorithms."; 6 | 7 | export const SEO = ({ subtitle }) => { 8 | return ( 9 | 15 | 16 | 17 | 21 | 22 | 23 | 24 | 25 | 26 | ); 27 | }; 28 | -------------------------------------------------------------------------------- /src/components/ThemeToggle.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Switch } from "@material-ui/core"; 3 | import { useThemeContext } from "../context"; 4 | 5 | export const ThemeToggle = () => { 6 | const { colorMode, toggleColorMode } = useThemeContext(); 7 | 8 | return ( 9 | 14 | ); 15 | }; 16 | -------------------------------------------------------------------------------- /src/components/index.js: -------------------------------------------------------------------------------- 1 | export * from "./AlgorithmModals"; 2 | export * from "./InformationModal"; 3 | export * from "./IntroductionModal"; 4 | export * from "./Layout"; 5 | export * from "./MapPlot"; 6 | export * from "./Menu"; 7 | export * from "./MenuHeader"; 8 | export * from "./MenuItem"; 9 | export * from "./MenuMetrics"; 10 | export * from "./MenuPointControls"; 11 | export * from "./MenuSection"; 12 | export * from "./MenuSolverControls"; 13 | export * from "../context/PreSetTheme"; 14 | export * from "./SEO"; 15 | export * from "./ThemeToggle"; 16 | export { default as CoordinateInput } from './CoordinateInput'; 17 | -------------------------------------------------------------------------------- /src/constants.js: -------------------------------------------------------------------------------- 1 | // orangish 2 | export const START_POINT_COLOR = [255, 87, 34]; 3 | 4 | // blueish 5 | export const POINT_COLOR = [41, 121, 255]; 6 | 7 | // greenish 8 | export const BEST_PATH_COLOR = [76, 175, 80]; 9 | 10 | // orangish 11 | export const EVALUATING_PATH_COLOR = [255, 87, 34, 225]; 12 | 13 | // reddish 14 | export const EVALUATING_ERROR_COLOR = [255, 25, 25, 240]; 15 | 16 | // greyish 17 | export const EVALUATING_SEGMENT_COLOR = [180, 180, 180, 240]; 18 | 19 | export const COLOR_MODE_KEY = "color-mode"; 20 | -------------------------------------------------------------------------------- /src/content/exhaustive/branchAndBoundOnCost.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: exhaustive 3 | order: 3 4 | solverKey: branchAndBoundOnCost 5 | friendlyName: Branch and Bound (Cost) 6 | defaults: 7 | evaluatingDetailLevel: 2 8 | maxEvaluatingDetailLevel: 2 9 | --- 10 | 11 | # Branch and Bound on Cost 12 | 13 | This is a recursive algorithm, similar to depth first search, that is guaranteed to find the optimal solution. 14 | 15 | The candidate solution space is generated by systematically traversing possible paths, and discarding large subsets of fruitless candidates by comparing the current solution to an upper and lower bound. In this case, the upper bound is the best path found so far. 16 | 17 | While evaluating paths, if at any point the current solution is already more expensive (longer) than the best complete path discovered, there is no point continuing. 18 | 19 | For example, imagine: 20 | 21 | 1. A -> B -> C -> D -> E -> A was already found with a cost of 100. 22 | 2. We are evaluating A -> C -> E, which has a cost of 110. There is **no point** evaluating the remaining solutions. 23 | 3. Instead of continuing to evaluate all of the child solutions from here, we can go down a different path, eliminating candidates not worth evaluating: 24 | - `A -> C -> E -> D -> B -> A` 25 | - `A -> C -> E -> B -> D -> A` 26 | 27 | Implementation is very similar to depth first search, with the exception that we cut paths that are already longer than the current best. 28 | 29 | ## Implementation 30 | 31 | ```javascript 32 | const branchAndBoundOnCost = async ( 33 | points, 34 | path = [], 35 | visited = null, 36 | overallBest = Infinity 37 | ) => { 38 | if (visited === null) { 39 | // initial call 40 | path = [points.shift()]; 41 | points = new Set(points); 42 | visited = new Set(); 43 | } 44 | 45 | // figure out which points are left 46 | const available = setDifference(points, visited); 47 | 48 | // calculate the cost, from here, to go home 49 | const backToStart = [...path, path[0]]; 50 | const cost = pathCost(backToStart); 51 | 52 | if (cost > overallBest) { 53 | // we may not be done, but have already traveled further than the best path 54 | // no reason to continue 55 | return [null, null]; 56 | } 57 | 58 | // still cheaper than the best, keep going deeper, and deeper, and deeper... 59 | 60 | if (available.size === 0) { 61 | // at the end of the path, return where we're at 62 | return [cost, backToStart]; 63 | } 64 | 65 | let [bestCost, bestPath] = [null, null]; 66 | 67 | // for every point yet to be visited along this path 68 | for (const p of available) { 69 | // go to that point 70 | visited.add(p); 71 | path.push(p); 72 | 73 | // RECURSE - go through all the possible points from that point 74 | const [curCost, curPath] = await branchAndBoundOnCost( 75 | points, 76 | path, 77 | visited, 78 | overallBest 79 | ); 80 | 81 | // if that path is better and complete, keep it 82 | if (curCost && (!bestCost || curCost < bestCost)) { 83 | [bestCost, bestPath] = [curCost, curPath]; 84 | 85 | if (!overallBest || bestCost < overallBest) { 86 | // found a new best complete path 87 | overallBest = bestCost; 88 | self.setBestPath(bestPath, bestCost); 89 | } 90 | } 91 | 92 | // go back up and make that point available again 93 | visited.delete(p); 94 | path.pop(); 95 | } 96 | 97 | return [bestCost, bestPath]; 98 | }; 99 | ``` 100 | -------------------------------------------------------------------------------- /src/content/exhaustive/branchAndBoundOnCostAndCross.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: exhaustive 3 | order: 4 4 | solverKey: branchAndBoundOnCostAndCross 5 | friendlyName: Branch and Bound (Cost, Crossings) 6 | defaults: 7 | evaluatingDetailLevel: 2 8 | maxEvaluatingDetailLevel: 2 9 | --- 10 | 11 | # Branch and Bound (Cost, Intersections) 12 | 13 | This is the same as branch and bound on cost, with an additional heuristic added to further minimize the search space. 14 | 15 | While traversing paths, if at any point the path intersects (crosses over) itself, than backtrack and try the next way. It's been proven that an optimal path will never contain crossings. 16 | 17 | Implementation is almost identical to branch and bound on cost only, with the added heuristic below: 18 | 19 | ## Implementation 20 | 21 | ```javascript 22 | 23 | const counterClockWise = (p, q, r) => { 24 | return (q[0] - p[0]) * (r[1] - q[1]) < 25 | (q[1] - p[1]) * (r[0] - q[0]) 26 | } 27 | 28 | const intersects = (a, b, c, d) => { 29 | return counterClockWise(a, c, d) !== counterClockWise(b, c, d) && 30 | counterClockWise(a, b, c) !== counterClockWise(a, b, d) 31 | } 32 | 33 | const branchAndBoundOnCostAndCross = async (...) => { 34 | // 35 | // ..... 36 | // 37 | 38 | if (path.length > 3) { 39 | // if this newly added edge crosses over the existing path, 40 | // don't continue. It's been proven that an optimal path will 41 | // not cross itself. 42 | const newSegment = [ 43 | path[path.length-2], path[path.length-1] 44 | ] 45 | for (let i=1; i { 39 | if (visited === null) { 40 | // initial call 41 | path = [points.shift()]; 42 | points = new Set(points); 43 | visited = new Set(); 44 | } 45 | 46 | // figure out what points are left from this point 47 | const available = setDifference(points, visited); 48 | 49 | if (available.size === 0) { 50 | // this must be a complete path 51 | const backToStart = [...path, path[0]]; 52 | 53 | // calculate the cost of this path 54 | const cost = pathCost(backToStart); 55 | 56 | // return both the cost and the path where we're at 57 | return [cost, backToStart]; 58 | } 59 | 60 | let [bestCost, bestPath] = [null, null]; 61 | 62 | // for every point yet to be visited along this path 63 | for (const p of available) { 64 | // go to that point 65 | visited.add(p); 66 | path.push(p); 67 | 68 | // RECURSE - go through all the possible points from that point 69 | const [curCost, curPath] = await dfs(points, path, visited, overallBest); 70 | 71 | // if that path is better, keep it 72 | if (bestCost === null || curCost < bestCost) { 73 | [bestCost, bestPath] = [curCost, curPath]; 74 | 75 | if (overallBest === null || bestCost < overallBest) { 76 | // found a new best complete path 77 | overallBest = bestCost; 78 | } 79 | } 80 | 81 | // go back up and make that point available again 82 | visited.delete(p); 83 | path.pop(); 84 | } 85 | return [bestCost, bestPath]; 86 | }; 87 | ``` 88 | -------------------------------------------------------------------------------- /src/content/exhaustive/random.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: exhaustive 3 | order: 2 4 | solverKey: random 5 | friendlyName: Random 6 | defaults: 7 | evaluatingDetailLevel: 1 8 | maxEvaluatingDetailLevel: 1 9 | --- 10 | 11 | # Random 12 | 13 | This is an impractical, albeit exhaustive algorithm. It is here only for demonstration purposes, but will not find a reasonable path for traveling salesman problems above 7 or 8 points. 14 | 15 | I consider it exhaustive because if it runs for infinity, eventually it will encounter every possible path. 16 | 17 | 1. From the starting path 18 | 2. Randomly shuffle the path 19 | 3. If it's better, keep it 20 | 4. If not, ditch it and keep going 21 | 22 | ## Implementation 23 | 24 | ```javascript 25 | const random = async points => { 26 | let best = Infinity; 27 | 28 | while (true) { 29 | // save off the starting point 30 | const start = points.shift(); 31 | 32 | // sort the remaining points 33 | const path = points.sort(() => Math.random() - 0.5); 34 | 35 | // put the starting point back 36 | path.unshift(start); 37 | 38 | // return to the starting point 39 | path.push(start); 40 | 41 | // calculate the new cost 42 | const cost = pathCost(path); 43 | 44 | if (cost < best) { 45 | // we found a better path 46 | best = cost; 47 | } 48 | 49 | // get rid of starting point at the end 50 | path.pop(); 51 | } 52 | }; 53 | ``` 54 | -------------------------------------------------------------------------------- /src/content/heurisitc-construction/arbitraryInsertion.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: heuristic-construction 3 | order: 2 4 | solverKey: arbitraryInsertion 5 | friendlyName: Arbitrary Insertion 6 | defaults: 7 | evaluatingDetailLevel: 1 8 | maxEvaluatingDetailLevel: 1 9 | --- 10 | 11 | # Heuristic Construction: Arbitrary Insertion 12 | 13 | This is a heuristic construction algorithm. It select a random point, and then figures out where the best place to put it will be. 14 | 15 | 1. From the starting point 16 | 2. First, go to the closest point 17 | 3. Choose a random point to go to 18 | 4. Find the cheapest place to add it in the path 19 | 5. Chosen point is no longer an "available point" 20 | 6. Continue from #3 until there are no available points, and then return to the start. 21 | 22 | ## Implementation 23 | 24 | ```javascript 25 | const arbitraryInsertion = async points => { 26 | // from the starting point 27 | const path = [points.shift()]; 28 | 29 | // 30 | // INITIALIZATION - go to the nearest point 31 | // 32 | points.sort((a, b) => distance(path[0], b) - distance(path[0], a)); 33 | path.push(points.pop()); 34 | 35 | // randomly sort points - this is the order they will be added 36 | // to the path 37 | points.sort(() => Math.random() - 0.5); 38 | 39 | while (points.length > 0) { 40 | // 41 | // SELECTION - choose a next point randomly 42 | // 43 | const nextPoint = points.pop(); 44 | 45 | // 46 | // INSERTION -find the insertion spot that minimizes distance 47 | // 48 | let [bestCost, bestIdx] = [Infinity, null]; 49 | for (let i = 1; i < path.length; i++) { 50 | const insertionCost = pathCost([path[i - 1], nextPoint, path[i]]); 51 | if (insertionCost < bestCost) { 52 | [bestCost, bestIdx] = [insertionCost, i]; 53 | } 54 | } 55 | path.splice(bestIdx, 0, nextPoint); 56 | } 57 | 58 | // return to start after visiting all other points 59 | path.push(path[0]); 60 | }; 61 | ``` 62 | -------------------------------------------------------------------------------- /src/content/heurisitc-construction/convexHull.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: heuristic-construction 3 | order: 5 4 | solverKey: convexHull 5 | friendlyName: Convex Hull 6 | defaults: 7 | evaluatingDetailLevel: 2 8 | maxEvaluatingDetailLevel: 2 9 | --- 10 | 11 | # Convex Hull 12 | 13 | This is a heuristic construction algorithm. It starts by building the [convex hull](https://en.wikipedia.org/wiki/Convex_hull), and adding interior points from there. This implmentation uses another heuristic for insertion based on the ratio of the cost of adding the new point to the overall length of the segment, however any insertion algorithm could be applied after building the hull. 14 | 15 | There are a number of algorithms to determine the convex hull. This implementation uses the [gift wrapping algorithm](https://en.wikipedia.org/wiki/Gift_wrapping_algorithm). 16 | 17 | In essence, the steps are: 18 | 19 | 1. Determine the leftmost point 20 | 2. Continually add the most counterclockwise point until the convex hull is formed 21 | 3. For each remaining point p, find the segment i => j in the hull that minimizes cost(i -> p) + cost(p -> j) - cost(i -> j) 22 | 4. Of those, choose p that minimizes cost(i -> p -> j) / cost(i -> j) 23 | 5. Add p to the path between i and j 24 | 6. Repeat from #3 until there are no remaining points 25 | 26 | ## Implementation 27 | 28 | ```javascript 29 | const convexHull = async points => { 30 | const sp = points[0]; 31 | 32 | // Find the "left most point" 33 | let leftmost = points[0]; 34 | for (const p of points) { 35 | if (p[1] < leftmost[1]) { 36 | leftmost = p; 37 | } 38 | } 39 | 40 | const path = [leftmost]; 41 | 42 | while (true) { 43 | const curPoint = path[path.length - 1]; 44 | let [selectedIdx, selectedPoint] = [0, null]; 45 | 46 | // find the "most counterclockwise" point 47 | for (let [idx, p] of points.entries()) { 48 | if (!selectedPoint || orientation(curPoint, p, selectedPoint) === 2) { 49 | // this point is counterclockwise with respect to the current hull 50 | // and selected point (e.g. more counterclockwise) 51 | [selectedIdx, selectedPoint] = [idx, p]; 52 | } 53 | } 54 | 55 | // adding this to the hull so it's no longer available 56 | points.splice(selectedIdx, 1); 57 | 58 | // back to the furthest left point, formed a cycle, break 59 | if (selectedPoint === leftmost) { 60 | break; 61 | } 62 | 63 | // add to hull 64 | path.push(selectedPoint); 65 | } 66 | 67 | while (points.length > 0) { 68 | let [bestRatio, bestPointIdx, insertIdx] = [Infinity, null, 0]; 69 | 70 | for (let [freeIdx, freePoint] of points.entries()) { 71 | // for every free point, find the point in the current path 72 | // that minimizes the cost of adding the point minus the cost of 73 | // the original segment 74 | let [bestCost, bestIdx] = [Infinity, 0]; 75 | for (let [pathIdx, pathPoint] of path.entries()) { 76 | const nextPathPoint = path[(pathIdx + 1) % path.length]; 77 | 78 | // the new cost minus the old cost 79 | const evalCost = 80 | pathCost([pathPoint, freePoint, nextPathPoint]) - 81 | pathCost([pathPoint, nextPathPoint]); 82 | 83 | if (evalCost < bestCost) { 84 | [bestCost, bestIdx] = [evalCost, pathIdx]; 85 | } 86 | } 87 | 88 | // figure out how "much" more expensive this is with respect to the 89 | // overall length of the segment 90 | const nextPoint = path[(bestIdx + 1) % path.length]; 91 | const prevCost = pathCost([path[bestIdx], nextPoint]); 92 | const newCost = pathCost([path[bestIdx], freePoint, nextPoint]); 93 | const ratio = newCost / prevCost; 94 | 95 | if (ratio < bestRatio) { 96 | [bestRatio, bestPointIdx, insertIdx] = [ratio, freeIdx, bestIdx + 1]; 97 | } 98 | } 99 | 100 | const [nextPoint] = points.splice(bestPointIdx, 1); 101 | path.splice(insertIdx, 0, nextPoint); 102 | } 103 | 104 | // rotate the array so that starting point is back first 105 | const startIdx = path.findIndex(p => p === sp); 106 | path.unshift(...path.splice(startIdx, path.length)); 107 | 108 | // go back home 109 | path.push(sp); 110 | }; 111 | ``` 112 | -------------------------------------------------------------------------------- /src/content/heurisitc-construction/furthestInsertion.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: heuristic-construction 3 | order: 4 4 | solverKey: furthestInsertion 5 | friendlyName: Furthest Insertion 6 | defaults: 7 | evaluatingDetailLevel: 1 8 | maxEvaluatingDetailLevel: 1 9 | --- 10 | 11 | # Furthest Insertion 12 | 13 | This is a heuristic construction algorithm. It selects the furthest point from the path, and then figures out where the best place to put it will be. 14 | 15 | 1. From the starting point 16 | 2. First, go to the closest point 17 | 3. Choose the point that is furthest from any of the points on the path 18 | 4. Find the cheapest place to add it in the path 19 | 5. Chosen point is no longer an "available point" 20 | 6. Continue from #3 until there are no available points, and then return to the start. 21 | 22 | ## Implementation 23 | 24 | ```javascript 25 | const furthestInsertion = async points => { 26 | // from the starting point 27 | const path = [points.shift()]; 28 | 29 | // 30 | // INITIALIZATION - go to the nearest point first 31 | // 32 | points.sort((a, b) => distance(path[0], b) - distance(path[0], a)); 33 | path.push(points.pop()); 34 | 35 | while (points.length > 0) { 36 | // 37 | // SELECTION - furthest point from the path 38 | // 39 | let [selectedDistance, selectedIdx] = [0, null]; 40 | for (const [freePointIdx, freePoint] of points.entries()) { 41 | // find the minimum distance to the path for freePoint 42 | let [bestCostToPath, costToPathIdx] = [Infinity, null]; 43 | for (const pathPoint of path) { 44 | const dist = distance(freePoint, pathPoint); 45 | if (dist < bestCostToPath) { 46 | [bestCostToPath, costToPathIdx] = [dist, freePointIdx]; 47 | } 48 | } 49 | 50 | // if this point is further from the path than the currently selected 51 | if (bestCostToPath > selectedDistance) { 52 | [selectedDistance, selectedIdx] = [bestCostToPath, costToPathIdx]; 53 | } 54 | } 55 | const [nextPoint] = points.splice(selectedIdx, 1); 56 | 57 | // 58 | // INSERTION - find the insertion spot that minimizes distance 59 | // 60 | let [bestCost, bestIdx] = [Infinity, null]; 61 | for (let i = 1; i < path.length; i++) { 62 | const insertionCost = pathCost([path[i - 1], nextPoint, path[i]]); 63 | if (insertionCost < bestCost) { 64 | [bestCost, bestIdx] = [insertionCost, i]; 65 | } 66 | } 67 | path.splice(bestIdx, 0, nextPoint); 68 | } 69 | 70 | // return to start after visiting all other points 71 | path.push(path[0]); 72 | }; 73 | ``` 74 | -------------------------------------------------------------------------------- /src/content/heurisitc-construction/nearestInsertion.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: heuristic-construction 3 | order: 3 4 | solverKey: nearestInsertion 5 | friendlyName: Nearest Insertion 6 | defaults: 7 | evaluatingDetailLevel: 1 8 | maxEvaluatingDetailLevel: 1 9 | --- 10 | 11 | # Furthest Insertion 12 | 13 | This is a heuristic construction algorithm. It selects the closest point to the path, and then figures out where the best place to put it will be. 14 | 15 | 1. From the starting point 16 | 2. First, go to the closest point 17 | 3. Choose the point that is **nearest** to the current path 18 | 4. Find the cheapest place to add it in the path 19 | 5. Chosen point is no longer an "available point" 20 | 6. Continue from #3 until there are no available points, and then return to the start. 21 | 22 | ## Implementation 23 | 24 | ```javascript 25 | const nearestInsertion = async points => { 26 | // from the starting point 27 | const path = [points.shift()]; 28 | 29 | // 30 | // INITIALIZATION - go to the nearest point first 31 | // 32 | points.sort((a, b) => distance(path[0], b) - distance(path[0], a)); 33 | path.push(points.pop()); 34 | 35 | while (points.length > 0) { 36 | // 37 | // SELECTION - nearest point to the path 38 | // 39 | let [selectedDistance, selectedIdx] = [Infinity, null]; 40 | for (const [freePointIdx, freePoint] of points.entries()) { 41 | for (const pathPoint of path) { 42 | const dist = distance(freePoint, pathPoint); 43 | if (dist < selectedDistance) { 44 | [selectedDistance, selectedIdx] = [dist, freePointIdx]; 45 | } 46 | } 47 | } 48 | 49 | // get the next point to add 50 | const [nextPoint] = points.splice(selectedIdx, 1); 51 | 52 | // 53 | // INSERTION - find the insertion spot that minimizes distance 54 | // 55 | let [bestCost, bestIdx] = [Infinity, null]; 56 | for (let i = 1; i < path.length; i++) { 57 | const insertionCost = pathCost([path[i - 1], nextPoint, path[i]]); 58 | if (insertionCost < bestCost) { 59 | [bestCost, bestIdx] = [insertionCost, i]; 60 | } 61 | } 62 | path.splice(bestIdx, 0, nextPoint); 63 | } 64 | 65 | // return to start after visiting all other points 66 | path.push(path[0]); 67 | }; 68 | ``` 69 | -------------------------------------------------------------------------------- /src/content/heurisitc-construction/nearestNeighbor.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: heuristic-construction 3 | order: 1 4 | solverKey: nearestNeighbor 5 | friendlyName: Nearest Neighbor 6 | defaults: 7 | evaluatingDetailLevel: 1 8 | maxEvaluatingDetailLevel: 1 9 | --- 10 | 11 | # Nearest Neighbor 12 | 13 | This is a heuristic, greedy algorithm also known as nearest neighbor. It continually chooses the best looking option from the current state. 14 | 15 | 1. From the starting point 16 | 2. sort the remaining available points based on cost (distance) 17 | 3. Choose the closest point and go there 18 | 4. Chosen point is no longer an "available point" 19 | 5. Continue this way until there are no available points, and then return to the start. 20 | 21 | ## Implementation 22 | 23 | ```javascript 24 | const nearestNeighbor = async points => { 25 | const path = [points.shift()]; 26 | 27 | while (points.length > 0) { 28 | // sort remaining points in place by their 29 | // distance from the last point in the current path 30 | points.sort( 31 | (a, b) => 32 | distance(path[path.length - 1], b) - distance(path[path.length - 1], a) 33 | ); 34 | 35 | // go to the closest remaining point 36 | path.push(points.pop()); 37 | } 38 | 39 | // return to start after visiting all other points 40 | path.push(path[0]); 41 | const cost = pathCost(path); 42 | }; 43 | ``` 44 | -------------------------------------------------------------------------------- /src/content/heurisitc-construction/simulatedAnnealing.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: heuristic-construction 3 | order: 6 4 | solverKey: simulatedAnnealing 5 | friendlyName: Simulated Annealing 6 | defaults: 7 | evaluatingDetailLevel: 1 8 | maxEvaluatingDetailLevel: 1 9 | --- 10 | 11 | # Simulated Annealing 12 | 13 | Simulated annealing (SA) is a probabilistic technique for approximating the global optimum of a given function. Specifically, it is a metaheuristic to approximate global optimization in a large search space for an optimization problem. 14 | 15 | For problems where finding an approximate global optimum is more important than finding a precise local optimum in a fixed amount of time, simulated annealing may be preferable to exact algorithms 16 | 17 | ## Implementation 18 | 19 | ```javascript 20 | const simulatedAnnealing = async points => { 21 | const sp = points[0]; 22 | const path = points; 23 | 24 | const tempCoeff = 25 | path.length < 10 26 | ? 1 - 1e-4 27 | : path.length < 15 28 | ? 1 - 1e-5 29 | : path.length < 25 30 | ? 1 - 1e-6 31 | : 1 - 5e-7; 32 | 33 | const deltaDistance = (aIdx, bIdx) => { 34 | const aPrev = (aIdx - 1 + path.length) % path.length; 35 | const aNext = (aIdx + 1 + path.length) % path.length; 36 | const bPrev = (bIdx - 1 + path.length) % path.length; 37 | const bNext = (bIdx + 1 + path.length) % path.length; 38 | let diff = 39 | distance(path[bPrev], path[aIdx]) + 40 | distance(path[aIdx], path[bNext]) + 41 | distance(path[aPrev], path[bIdx]) + 42 | distance(path[bIdx], path[aNext]) - 43 | distance(path[aPrev], path[aIdx]) - 44 | distance(path[aIdx], path[aNext]) - 45 | distance(path[bPrev], path[bIdx]) - 46 | distance(path[bIdx], path[bNext]); 47 | 48 | if (bPrev === aIdx || bNext === aIdx) { 49 | diff += 2 * distance(path[aIdx], path[bIdx]); 50 | } 51 | return diff; 52 | }; 53 | 54 | const changePath = temperature => { 55 | // 2 random points 56 | const a = 1 + Math.floor(Math.random() * (path.length - 1)); 57 | const b = 1 + Math.floor(Math.random() * (path.length - 1)); 58 | 59 | const delta = deltaDistance(a, b); 60 | if (delta < 0 || Math.random() < Math.exp(-delta / temperature)) { 61 | // swap points 62 | [path[a], path[b]] = [path[b], path[a]]; 63 | } 64 | }; 65 | 66 | const initialTemp = 100 * distance(path[0], path[1]); 67 | let i = 0; 68 | 69 | for ( 70 | let temperature = initialTemp; 71 | temperature > 1e-6; 72 | temperature *= tempCoeff 73 | ) { 74 | changePath(temperature); 75 | if (i % 10000 == 0) { 76 | self.setEvaluatingPaths(() => ({ 77 | paths: [{ path, color: EVALUATING_PATH_COLOR }], 78 | cost: pathCost(path) 79 | })); 80 | await self.sleep(); 81 | } 82 | if (i % 100000 == 0) { 83 | path.push(sp); 84 | self.setBestPath(path, pathCost(path)); 85 | path.pop(); 86 | } 87 | i++; 88 | } 89 | 90 | // rotate the array so that starting point is back first 91 | rotateToStartingPoint(path, sp); 92 | 93 | // go back home 94 | path.push(sp); 95 | const cost = pathCost(path); 96 | 97 | self.setEvaluatingPaths(() => ({ 98 | paths: [{ path }], 99 | cost 100 | })); 101 | self.setBestPath(path, cost); 102 | }; 103 | 104 | makeSolver(simulatedAnnealing); 105 | ``` 106 | -------------------------------------------------------------------------------- /src/content/heuristic-improvement/twoOptInversion.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: heuristic-improvement 3 | order: 1 4 | solverKey: twoOptInversion 5 | friendlyName: Two Opt Inversion 6 | defaults: 7 | evaluatingDetailLevel: 1 8 | maxEvaluatingDetailLevel: 1 9 | --- 10 | 11 | # Two-Opt inversion 12 | 13 | This algorithm is also known as 2-opt, 2-opt mutation, and cross-aversion. The general goal is to find places where the path crosses over itself, and then "undo" that crossing. It repeats until there are no crossings. A characteristic of this algorithm is that afterwards the path is guaranteed to have no crossings. 14 | 15 | 1. While a better path has not been found. 16 | 2. For each pair of points: 17 | 3. Reverse the path between the selected points. 18 | 4. If the new path is cheaper (shorter), keep it and continue searching. Remember that we found a better path. 19 | 5. If not, revert the path and continue searching. 20 | 21 | ## Implementation 22 | 23 | ```javascript 24 | const twoOptInversion = async path => { 25 | path.push(path[0]); 26 | let best = pathCost(path); 27 | let swapped = true; 28 | 29 | while (swapped) { 30 | swapped = false; 31 | for (let pt1 = 1; pt1 < path.length - 1; pt1++) { 32 | for (let pt2 = pt1 + 1; pt2 < path.length - 1; pt2++) { 33 | // section of the path to reverse 34 | const section = path.slice(pt1, pt2 + 1); 35 | 36 | // reverse section in place 37 | section.reverse(); 38 | 39 | // replace section of path with reversed section in place 40 | path.splice(pt1, pt2 + 1 - pt1, ...section); 41 | 42 | // calculate new cost 43 | const newPath = path; 44 | const cost = pathCost(newPath); 45 | 46 | if (cost < best) { 47 | // found a better path after the swap, keep it 48 | swapped = true; 49 | best = cost; 50 | self.setBestPath(newPath, best); 51 | } else { 52 | // un-reverse the section 53 | section.reverse(); 54 | path.splice(pt1, pt2 + 1 - pt1, ...section); 55 | } 56 | } 57 | } 58 | } 59 | }; 60 | ``` 61 | -------------------------------------------------------------------------------- /src/content/heuristic-improvement/twoOptReciprocalExchange.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: heuristic-improvement 3 | order: 2 4 | solverKey: twoOptReciprocalExchange 5 | friendlyName: Two Opt Reciprocal Exchange 6 | defaults: 7 | evaluatingDetailLevel: 1 8 | maxEvaluatingDetailLevel: 1 9 | --- 10 | 11 | # Two-Opt Reciprocal Exchange 12 | 13 | This algorithm is similar to the 2-opt mutation or inversion algorithm, although generally will find a less optimal path. However, the computational cost of calculating new solutions is less intensive. 14 | 15 | The big difference with 2-opt mutation is not reversing the path between the 2 points. This algorithm is **not** always going to find a path that doesn't cross itself. 16 | 17 | It could be worthwhile to try this algorithm prior to 2-opt inversion because of the cheaper cost of calculation, but probably not. 18 | 19 | 1. While a better path has not been found. 20 | 2. For each pair of points: 21 | 3. Swap the points in the path. That is, go to point B before point A, continue along the same path, and go to point A where point B was. 22 | 4. If the new path is cheaper (shorter), keep it and continue searching. Remember that we found a better path. 23 | 5. If not, revert the path and continue searching. 24 | 25 | ## Implementation 26 | 27 | ```javascript 28 | const twoOptReciprocalExchange = async path => { 29 | path.push(path[0]); 30 | let best = pathCost(path); 31 | let swapped = true; 32 | 33 | self.setBestPath(path, best); 34 | 35 | while (swapped) { 36 | swapped = false; 37 | for (let pt1 = 1; pt1 < path.length - 1; pt1++) { 38 | for (let pt2 = pt1 + 1; pt2 < path.length - 1; pt2++) { 39 | // swap current pair of points 40 | [path[pt1], path[pt2]] = [path[pt2], path[pt1]]; 41 | 42 | // calculate new cost 43 | const cost = pathCost(path); 44 | 45 | if (cost < best) { 46 | // found a better path after the swap, keep it 47 | swapped = true; 48 | best = cost; 49 | } else { 50 | // swap back - this one's worse 51 | [path[pt1], path[pt2]] = [path[pt2], path[pt1]]; 52 | } 53 | } 54 | } 55 | } 56 | }; 57 | ``` 58 | -------------------------------------------------------------------------------- /src/content/introduction.md: -------------------------------------------------------------------------------- 1 | --- 2 | type: introduction 3 | --- 4 | 5 | # Traveling Salesman Problem 6 | 7 | The traveling salesman problem (TSP) asks the question, "Given a list of cities and the distances between each pair of cities, what is the shortest possible route that visits each city and returns to the origin city?". 8 | 9 | ### This project 10 | 11 | - The goal of this site is to be an **educational** resource to help visualize, learn, and develop different algorithms for the traveling salesman problem in a way that's easily accessible 12 | - As you apply different algorithms, the current best path is saved and used as input to whatever you run next. (e.g. shortest path first -> branch and bound). The order in which you apply different algorithms to the problem is sometimes referred to the meta-heuristic strategy. 13 | 14 | ### Heuristic algorithms 15 | 16 | Heuristic algorithms attempt to find a good approximation of the optimal path within a more _reasonable_ amount of time. 17 | 18 | **Construction** - Build a path (e.g. shortest path) 19 | 20 | - Shortest Path 21 | - Arbitrary Insertion 22 | - Furthest Insertion 23 | - Nearest Insertion 24 | - Convex Hull Insertion\* 25 | - Simulated Annealing\* 26 | 27 | **Improvement** - Attempt to take an existing constructed path and improve on it 28 | 29 | - 2-Opt Inversion 30 | - 2-Opt Reciprcal Exchange\* 31 | 32 | ### Exhaustive algorithms 33 | 34 | Exhaustive algorithms will always find the best possible solution by evaluating every possible path. These algorithms are typically significantly more expensive then the heuristic algorithms discussed next. The exhaustive algorithms implemented so far include: 35 | 36 | - Random Paths 37 | - Depth First Search (Brute Force) 38 | - Branch and Bound (Cost) 39 | - Branch and Bound (Cost, Intersections)\* 40 | 41 | ## Dependencies 42 | 43 | These are the main tools used to build this site: 44 | 45 | - [gatsbyjs](https://www.gatsbyjs.org) 46 | - [reactjs](https://reactjs.org) 47 | - [web workers](https://developer.mozilla.org/en-US/docs/Web/API/Web_Workers_API) 48 | - [material-ui](https://material-ui.com/) 49 | - [deck.gl](https://deck.gl/#/) 50 | - [mapbox](https://www.mapbox.com/) 51 | 52 | ## Contributing 53 | 54 | Pull requests are always welcome! Also, feel free to raise any ideas, suggestions, or bugs as an issue. 55 | -------------------------------------------------------------------------------- /src/context/PreSetTheme.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export const PreSetTheme = () => ( 4 |