7 ms·
Solving NP-hard puzzles with the oldest trick in the book
- master_yoda_1 5y agoI request the moderator of hn to please flag this post. This fraud is claiming to solve np hard problem in title and some jokers at hn make it to the front page of hn
- gwern 5y agoCould you be more specific about the fraud? Optimizing your code to use appropriate data structures, and using state reduction to remove symmetries and duplicate states seems like a perfectly cromulent way of reducing a problem to its true core, which may be small enough to bruteforce.
- master_yoda_1 5y agoClaiming to solve np hard problem is a fraud. Just put optimizing code in title.
- throwaway81523 5y ago> Claiming to solve np hard problem is a fraud. Claiming to solve an np hard problem in polynomial time on all inputs would be either a fraud or a breakthrough. This is not such a claim. The algorithm is organized to perform well on many but not all inputs--its worst case is exponential time and it doesn't pretend to be otherwise. If you play chess against a chess engine like Stockfish, the exact same thing is going on, and in fact the algorithms involved are closely related to the one in the article.
- Delk 5y agoI didn't read the title that way. You can have a (heuristic) solver that finds solutions to some instances of an NP-hard problem in reasonable time. That's not the same as claiming to have an algorithm that, in the strict theoretical CS sense, would solve that problem (i.e. all instances of it) in polynomial time. The latter claim would be highly suspicious by default; the former need not be, as it doesn't imply anything extraordinary. Quote from OP in another comment: > I think a better way to phrase it is that we are writing a solver for a reasonable subset of inputs to an NP-hard problem.
- master_yoda_1 5y agoMany expert spend life on approximation of np hard problem. For the name of holy God if you don’t understand something then please don’t trivialize it.
- perl4ever 5y agoWas the headline changed from "problem" to "puzzle"?
- lostmsu 5y agoIs this even a NP-hard problem?
- taintegral 5y agoI would really enjoy a proof that it is not!
- kbelder 5y agoPeople solve specific np-hard problems all the time, every day. They just don't solve THE np-hard problem.
- perl4ever 5y agoI'd suggest that maybe "puzzle" has a slightly different connotation than "problem". I think a "puzzle" is a concrete instance of a more formal and abstract "problem". So I don't think claiming to solve an instance (or many!) of a problem is inappropriate.
- coolgeek 5y agoThis is a good article. It deserves a lot more attention than it got.
- taintegral 5y agoThank you! As long as some people got to see it, that's enough for me. :)
- dang 5y agoWe'll put it in the second-chance pool (https://news.ycombinator.com/pool https://news.ycombinator.com/pool, explained at https://news.ycombinator.com/item?id=26998308 https://news.ycombinator.com/item?id=26998308), so it will get a random placement on HN's front page. p.s. if you don't mind, could you please put your email address in your profile? That way we can send you a repost invite in the future, which is the way we do this if the post is older than a few days. And even if it's not that old, we sometimes still email a heads-up.
- taintegral 5y agoDone! Thanks for the boost, I appreciate it very much. :)
- RicoElectrico 5y agoThe frequency of using that workaround is telling how much valuable content gets under the radar of the HN hivemind. If a submission is 1) not about FAANG, entrepreneurship, programming language du jour, current events, or HN's idols or 2) is posted from the wrong time zone - it's often dead on arrival. I mean, there are counterexamples on the frontpage, but it's only a tip of the iceberg compared to what you can get in /new after filtering all the spam and fluff. HN implicitly positions itself as "the smarter Reddit" but in my experience most subreddits of value don't have strong time zone bias. Anything that doesn't force me to post in the "SV programmers are slacking off" time window and compete for attention with a bazillion other posts would be welcome.
- matsemann 5y agoThe transitions can often be sped up by not cloning and modifying state, but instead keeping track of all transitions and rollbacking. Not sure if it's doable here, since undoing a LEFT cannot know if the box was already the wall. So might need some extra bookkeeping. But for instance when solving 8-queens, sudoku or similar for huge grids, just walking back up the tree of transitions and undoing and reapplying stuff yields an immense speedup. Edit: I see Radim mentions the same in a response to someone else.
- antman 5y agoReally great write up, some comment in it why in A* we need the heuristic to be the way it is. A* is a generic algorithm but given other problem knowledge i.e. the flat game map, an alternative algorithm selection and testing process would be a great addition. Overall great though.
- veselin 5y agoThis reminds me of the problems we did 15-20 ago at IOI or ACM ICPC. We did these in pure C then, sometimes C++. I would have kept the states in a different way. Instead of making a vector/array of actors, I would make a pair of bitvectors the size of the grid. 1 is set if there is a blue (resp. red) actor at that position. No sorting is needed and it seems that for more practical puzzles this gives smaller state. All move operations are still easy to implement.
- taintegral 5y agoThat would definitely work, and I’d be interested in the performance impact. This was written so that the state size would scale with the number of actors rather than the size of the grid. There is a degenerate case where a massive mostly empty grid becomes difficult not only to store in memory, but also to transition on move. The transition function would take time proportional to the size of the grid rather than the number of actors.
- lalaland1125 5y agoI think it would be super neat to also compare this to a generic SAT solver based solution.
- sriram_malhar 5y agoVery nicely written. In addition, the example chosen was itself lovely to play with. Explicit-state model checkers do this at scale. Readers may be interested in the internals of the TLA+ model checker, esp. the encoding of the state and dealing with disk. Model Checking TLA+ Specifications by Yuan Yu, Panagiotis Manolios, and Leslie Lamport (1999) https://lamport.azurewebsites.net/pubs/yuanyu-model-checking.ps https://lamport.azurewebsites.net/pubs/yuanyu-model-checking...
- petters 5y agoHow is this puzzle NP-hard? Genuine question, because of the number of pieces is bounded, it's solvable with a shortest path in a graph with a polynomial number of nodes.
- taintegral 5y agoBounding the maximum number of actors is just an optimization for the cases we want to solve. Of course if you wanted to really solve any case you would need infinite space, and that’s not achievable either. If you desired, you can also just omit that particular optimization. :) I think a better way to phrase it is that we are writing a solver for a reasonable subset of inputs to an NP-hard problem.
- deleted 5y ago[deleted]
- contravariant 5y agoWouldn't the size of the graph grow something like $n^k / k!$ ? This is not polynomial in $k$.
- lostmsu 5y agolim (k -> 0) (n^k / (k!)) is actually 0. k! grows faster than n^k for any constant n.
- taintegral 5y agoReally not sure why the state space would only grow as n^k / k!. As well as what n and k are in this case. Adding more tiles or more actors would both dramatically increase the size of the state space for that input.
- lostmsu 5y agoThat question should go to the parent comment. I only corrected the assumption. On your comment though, I don't think there's much of "drama" in increasing the state space. Really it is just under 2 bits per cell by width by height. I would say it grows exponentially to the size of the board.
- myaccount80 5y agoVery well written. Unfortunately I already tried all these kind of tricks in some competitions but others were still able to beat my code. Are there additional resources for improving it even further?
- Radim 5y agoCache + avoid dynamic allocations like the plague. For example, high-perf search algos implement reversible transitions: instead of creating & pushing new states around all the time, modify just one state, in-place. And then apply the same transformation in reverse when backtracking. If you design your data structures well – to reflect the required transitions and query operations, rather than what the problem looks like to a human when drawn on a piece of paper – the forward/backward transition is nearly a no-op. Just some binary bit fiddling over data that's already in a CPU cache. And there's NO DYNAMIC ALLOCATIONS at all. Your search will fly! The OP also mentions another great "caching" technique, under "Entropy reduction". This is really hard but basically try to find symmetries in the search space which allow you to prune away entire subspaces apriori, without searching at all. Often it'll be something like "rotating by 90 degrees leads to the same position", mirror positions, invariance to color, time… the symmetry types and their runtime benefits are problem-specific, so you need to sit and think hard about what makes a solution unique. In the limit, you may be lucky enough to prune away so much of the solution space that there's only a single state left. Congratulations: you've solved the problem analytically :)
- taintegral 5y agoIn my experience, the only way to make meaningful progress on performance from here on out is to: - Squeeze out more entropy (for example, rotating states for symmetric boards) - Make the heuristic function smarter (for example, by calculating the assignment bottleneck) I wrote a Carcassonne solver once and found many little optimization opportunities by detecting fail states early for example. Avoiding dead ends saves a massive amount of time.
- sam0x17 5y agoHaven't looked at this particular game closely but had a similar experience to OP with an Othello AI competition back in college. There were four killer features for me that allowed me to win (and to apparently keep winning for years after I left, according to my prof) 1. having a really good and creative heuristic. The one I used ended up taking 6 different ideas I had for heuristics and combining them together in a weighted average based on their performance in a randomized trial I conducted between the 6. My vague recollection is that slightly over-valuing 4-corners positions performs unexpectedly well in Othello, but there was a lot more to it than that. The actual effectiveness of various heuristics changes over time as the game goes on, though I never modeled or attempted to exploit this. 2. Knowing the exact memory and execution time bounds on my prof's machine and setting things up so that I can terminate exactly when the time is ~5ms away from running out. We were limited to exactly 1 second per turn. 3. Caching. This was especially important in my case since I was technically using 6 different heuristics. I actually pre-generated a cache of the 100 most popular gamestates I encountered during my randomized trials, and this vastly increased the average depth I was able to explore in the allotted calculation time for one turn (1 second), especially during early game. 4. This is a continuation of 3, but it's super important if you have a turn based game with execution time limits to not throw away your work between turns. If you can modify your search so that it is pausible / resumable (which you can do with some rather simple multi-threading), and then define a simple routine that lets you resume a previous search by quickly modifying the tree and then resuming instead of starting an entirely new one, you are going to explore much much more. This optimization even with a crappy heuristic is going to win 99% of the time against opponents who don't use it. One thing I didn't explore but wish I had was trying to predict which heuristic in my library of heuristics is closest to that of my opponent, and then opting for a strategy that is most likely to beat that heuristic. This would look something like you calculate each turn what the most likely opponent heuristic is based on their moves so far, and then have a pre-computed table of each heuristic's "foil". Maybe this would only kick in after several turns. An even better version of this would probably be to just use the probabilities for each heuristic as the weighted importance of each respective foil, and use all the foils together in a weighted average. Fun fact: this was all in Java at the time. I can only imagine what havoc one could wreck with this sort of approach in Rust.
- t3chn0l0g1c 5y agoVery nice article. I´ve spent quite some time optimizing a solver for https://www.pathery.com/ https://www.pathery.com/, but due the insanely large search space in the larger puzzles its quite not as easy. Can recommend that for anyone looking for a challenge.
- grandpa 5y agogit clone --branch start https://github.com/djkoloski/anima_solver Cloning into 'anima_solver'... fatal: Remote branch start not found in upstream origin
- taintegral 5y agoThis is fixed now.
- robinhouston 5y agoThis is an interesting puzzle. It not obviously in NP. My guess would be that it’s PSPACE-complete. Have you thought about the complexity at all?
- taintegral 5y agoI gave the thought some idle time, but it's been so long since I've constructed a proper hardness proof. If I do, I'll definitely make a post about it!
- dietrichepp 5y agoNice article! I used similar techniques to find solutions for a game called “DStar”… including the choice to write the solver in Rust. You can play the game and see the optimal solutions on the web (not tested on mobile): https://www.moria.us/games/dstar/play https://www.moria.us/games/dstar/play A state in this game is: enum Active { Ball, Block, } struct State { coins: u32, // bitmask of which coins remain active: Active, // which player is active ball: Point, // location of ball block: Point, // location of block } I thought about writing an A* solver for this, but a simple BFS found all the solutions quickly enough. With a single-threaded solver, each level could be solved in 40s or less. The longest solutions are around 100 moves long, and the entire set of 25 levels is solved with 2.5 minutes of CPU time.
- pcvonz 5y agoOne small suggestion, I initially skimmed the first part of the article and didn't know the game worked on mobile. You could add an overlay to the game before you tap/click it to display the controls. I'm going to go back to reading it now :)
- thealig 5y agovery nice, enjoyed playing around the game examples. Is there a repository where such kind of puzzle games come from?