Welcome to my blog, which was once a mailing list of the same name and is still generated by mail. Please reply via the "comment" links.
Always interested in offers/projects/new ideas. Eclectic experience in fields like: numerical computing; Python web; Java enterprise; functional languages; GPGPU; SQL databases; etc. Based in Santiago, Chile; telecommute worldwide. CV; email.
Choochoo Training Diary
Immediate attention needed for your account [ Ticket no: 91833294697018 ]; Reddit Comment on Fascism + Trump; Surprise Paradox; [Books] Good Author List; [Computing] Efficient queries with grouping in Postgres; [Computing] Automatic Wake (Linux); [Computing] AWS CDK Aspects in Go; [Bike] Adidas Gravel Shoes; [Computing, Horror] Biological Chips; [Books] Weird Lit Recs; [Covid] Extended SIR Models; [Art] York-based Printmaker; [Physics] Quantum Transitions are not Instantaneous; [Computing] AI and Drum Machines; [Computing] Probabilities, Stopping Times, Martingales; bpftrace Intro Article; [Computing] Starlab Systems - Linux Laptops; [Computing] Extended Berkeley Packet Filter; [Green] Mainspring Linear Generator; Better Approach; Rummikub Solver; Chilean Poetry; [Bike] Fixing Spyre Brakes (That Need Constant Adjustment); [Computing, Music] Raspberry Pi Media (Audio) Streamer; [Computing] Amazing Hack To Embed DSL In Python; [Bike] Ruta Del Condor (El Alfalfal); [Bike] Estimating Power On Climbs; [Computing] Applying Azure B2C Authentication To Function Apps; [Bike] Gearing On The Back Of An Envelope; [Computing] Okular and Postscript in OpenSuse; There's a fix!; [Computing] Fail2Ban on OpenSuse Leap 15.3 (NFTables); [Cycling, Computing] Power Calculation and Brakes; [Hardware, Computing] Amazing Pockit Computer; Bullying; How I Am - 3 Years Post Accident, 8+ Years With MS; [USA Politics] In America's Uncivil War Republicans Are The Aggressors; [Programming] Selenium and Python; Better Walking Data; [Bike] How Fast Before Walking More Efficient Than Cycling?; [COVID] Coronavirus And Cycling; [Programming] Docker on OpenSuse; Cadence v Speed; [Bike] Gearing For Real Cyclists; [Programming] React plotting - visx; [Programming] React Leaflet; AliExpress Independent Sellers; Applebaum - Twilight of Democracy; [Politics] Back + US Elections; [Programming,Exercise] Simple Timer Script; [News] 2019: The year revolt went global; [Politics] The world's most-surveilled cities; [Bike] Hope Freehub; [Restaurant] Mama Chau's (Chinese, Providencia); [Politics] Brexit Podcast; [Diary] Pneumonia; [Politics] Britain's Reichstag Fire moment; install cairo; [Programming] GCC Sanitizer Flags; [GPU, Programming] Per-Thread Program Counters; My Bike Accident - Looking Back One Year; [Python] Geographic heights are incredibly easy!; [Cooking] Cookie Recipe; Efficient, Simple, Directed Maximisation of Noisy Function; And for argparse; Bash Completion in Python; [Computing] Configuring Github Jekyll Locally; [Maths, Link] The Napkin Project; You can Masquerade in Firewalld; [Bike] Servicing Budget (Spring) Forks; [Crypto] CIA Internet Comms Failure; [Python] Cute Rate Limiting API; [Causality] Judea Pearl Lecture; [Security, Computing] Chinese Hardware Hack Of Supermicro Boards; SQLAlchemy Joined Table Inheritance and Delete Cascade; [Translation] The Club; [Computing] Super Potato Bruh; [Computing] Extending Jupyter; Further HRM Details; [Computing, Bike] Activities in ch2; [Books, Link] Modern Japanese Lit; What ended up there; [Link, Book] Logic Book; Update - Garmin Express / Connect; Garmin Forerunner 35 v 230; [Link, Politics, Internet] Government Trolls; [Link, Politics] Why identity politics benefits the right more than the left; SSH Forwarding; A Specification For Repeating Events; A Fight for the Soul of Science; [Science, Book, Link] Lost In Math; OpenSuse Leap 15 Network Fixes; Update; [Book] Galileo's Middle Finger; [Bike] Chinese Carbon Rims; [Bike] Servicing Shimano XT Front Hub HB-M8010; [Bike] Aliexpress Cycling Tops; [Computing] Change to ssh handling of multiple identities?; [Bike] Endura Hummvee Lite II; [Computing] Marble Based Logic; [Link, Politics] Sanity Check For Nuclear Launch
© 2006-2025 Andrew Cooke (site) / post authors (content).
From: "andrew cooke" <andrew@...>
Date: Wed, 16 May 2007 20:00:24 -0400 (CLT)
% a parallel sudoku solver. each cell on the grid is a separate % process, aware of - and negotiating with - its "neighbours" (those % cells it must not conflict with). % a cell is very simple - it contains just two integer values as % "mutable" state: the current digit and a "confidence" in that value. % the negotiation protocol is defined below. cells "ping" the % controller when the exchange results in a change of value. the % controller stops the system when there has been no change in any % value for CONTROL_TIMEOUT (currently 1s). % this is terribly, terribly brain-dead and inefficient (takes several % - sometimes tens of - minutes to solve the test case on my newish % linux box). tuning the doubt parameter may help slightly (a value % of 0.1 seem to work). % i think some very interesting graphics could be generated from this % code - think of the solution as a kind of crystallisation, with % competing centres of nucleation. please contact me % (andrew@...) if interested... -module(sudoku). -export([solve/2, empty/1, starting/3, norvig/2]). -export([neighbours/1, format/2]). -define(CELL_TIMEOUT, 1). % see comments below -define(CONTROL_TIMEOUT, 1000). % shutdown if no changes after this -define(RANGE, lists:seq(1, 9)). % [1 ... 9] -record(cell, {locn, nbrs, dbt}). % configuration data for a cell % negotiation between cells is described below but first we need to % get everything up and running. solve(Puzzle, Doubt) -> Cells = [{X, Y} || Y <- ?RANGE, X <- ?RANGE], %application:start(sasl), % debug info for failed processes register(ctrl, self()), [new_cell(Doubt, Puzzle, Cell) || Cell <- Cells], broadcast(Cells, start), ok = wait_to_complete(), Result = [report(Cell) || Cell <- Cells], broadcast(Cells, stop), % i don't understand why flatten is needed here {Result, lists:flatten(lists:foldl(fun format/2, "", Result))}. broadcast(Cells, Message) -> [address(Cell) ! Message || Cell <- Cells]. % for easy identification we use the string {X,Y}, converted to an % atom, as the name for each process. address(Locn) -> list_to_atom(lists:flatten(io_lib:format("~p", [Locn]))). new_cell(Doubt, Puzzle, Locn) -> {Value, Confidence} = initialise(Puzzle, Locn), Cell = #cell{locn = Locn, nbrs = named_neighbours(Locn), dbt = Doubt}, % create the process and register its name register(address(Locn), proc_lib:spawn(sudoku, starting, [Cell, Value, Confidence])). % a puzzle is represented as a map (implemented in gb_trees) from the % location to the value. only cells with initial values appear in the % map. initialise(Puzzle, Locn) -> case gb_trees:is_defined(Locn, Puzzle) of true -> {gb_trees:get(Locn, Puzzle), certain}; false -> {random:uniform(9), 0} end. named_neighbours(Locn) -> [address(N) || N <- neighbours(Locn)]. % cells that can potentially conflict with this cell neighbours({X, Y}) -> CornerX = 3 * ((X - 1) div 3), CornerY = 3 * ((Y - 1) div 3), [{XX, YY} || XX <- [CornerX + P || P <- [1, 2, 3]], YY <- [CornerY + Q || Q <- [1, 2, 3]], {XX, YY} /= {X, Y}] ++ [{XX, YY} || XX <- [roll(X + P) || P <- [0, 3, 6]], YY <- [roll(Y + Q) || Q <- [0, 3, 6]], {XX, YY} /= {X, Y}]. roll(N) when N > 9 -> N - 9; roll(N) when N < 1 -> N + 9; roll(N) -> N. % each process is a simple state machine. this is the starting state, % waiting on an initial message (so that all the processes can be % deployed before they try talking to each other). starting(Cell, Value, Confidence) -> receive start -> searching(Cell, Value, Confidence) end. % the final state, allowing for clean shutdown. ending() -> receive stop -> ok end. % the intermediate state (repeated) searching(Cell, Value, Confidence) -> receive % shutdown takes priority over other, pending messages report -> report(Value, Confidence), ending(); % otherwise, if we have a waiting message, process it Message -> transition(Cell, Value, Confidence, Message) after % otherwise, after a short pause, send a message to a % random neighbour asserting our current value. ?CELL_TIMEOUT -> random_nbr(Cell) ! {assert, self(), Value, Confidence}, searching(Cell, Value, Confidence) end. random_nbr(Cell) -> lists:nth(random:uniform(length(Cell#cell.nbrs)), Cell#cell.nbrs). % next is our transition table for the negotiation protocol (the % protocol is symmetric - our neighbour has the same table). for each % message we reply if necessary (we either deny or yield to an % assertion) and then update our internal state (Value and % Confidence). % note that we *never* change the Value if we agree with our % neighbour. this ensures that the process is stationary (if we have % a solution we don't mess it up) which, together with a quasi-random % exploration of the available parameter space, is pretty much our % only (weak!) guarantee for convergence. % the Confidence value (together with the "doubt" parameter for the % run) is an optimisation to help guide us to a solution more quickly. % if we are certain, and our neighbour is in conflict, deny them. transition(Cell, Value, certain, {assert, From, Value, _Test}) -> deny(From, Value), searching(Cell, Value, certain); % if our neighbour is certain, and we are conflict, change. note that % we initially have no confidence in the new value. transition(Cell, Value, _Confidence, {assert, From, Value, certain}) -> yield(From, Value), searching(Cell, new_value(Cell, Value), 0); % otherwise, conflict is resolved according to relevant confidence % levels. in this case, the neighbour is more confident than we are, % so we yield and change. transition(Cell, Value, Confidence, {assert, From, Value, Test}) when Confidence < Test -> yield(From, Value), searching(Cell, new_value(Cell, Value), 0); % alternatively, if we are more confident, we deny them the value. % this is where doubt comes into play.... % on an emotional level, this is simple enough - our confidence should % decrease if our neighbours continually disagree with us. but i % think there is a more technical issue, too. it is possible for a % set of cells to be self-consistent and so, by mutual assurances, % gain a high level of confidence. for "hard" sudoku games i suspect % that such a group can exist without conflicting directly with any % known (initial) value. in such cases the "self-deluding" set can % become unbeatable (their confidence is higher than any neighbour), % blocking a solution. a sufficiently high level of doubt weakens % this process and increases our chance of global convergence. transition(Cell, Value, Confidence, {assert, From, Value, _Test}) -> deny(From, Value), searching(Cell, Value, doubt(Cell, Confidence)); % if our neighbour claims a different value from our own then we agree % - this should also increase our confidence slightly. transition(Cell, Value, Confidence, {assert, From, Other, _Test}) -> yield(From, Other), searching(Cell, Value, confirm(Confidence)); % if our neighbour has denied us a value (they were more certain than % us on the receipt of our assertion), then we must change... transition(Cell, Value, _Confidence, {deny, Value}) -> searching(Cell, new_value(Cell, Value), 0); % ...but not if we already changed and no longer have the value we % asserted! transition(Cell, Value, Confidence, {deny, _Other}) -> searching(Cell, Value, Confidence); % if our neighbour acquiesces with our assertion we can increase our % confidence a little... transition(Cell, Value, Confidence, {yield, Value}) -> searching(Cell, Value, confirm(Confidence)); % ...but if they agreed with a value we no longer have then we can do % little about it. transition(Cell, Value, Confidence, {yield, _Other}) -> searching(Cell, Value, Confidence). % the various actions used above deny(From, Value) -> From ! {deny, Value}. yield(From, Value) -> From ! {yield, Value}. report(Value, Confidence) -> ctrl ! {result, Value, Confidence}. new_value(Cell, Value) -> case random:uniform(9) of Value -> new_value(Cell, Value); Other -> ctrl ! {tick, Cell#cell.locn, Value, Other}, searching(Cell, Other, 0) end. doubt(_Cell, certain) -> certain; doubt(Cell, Confidence) -> lists:max([0, trunc(Confidence * Cell#cell.dbt)]). confirm(certain) -> certain; confirm(Confidence) -> Confidence + 1. % shutdown and reporting wait_to_complete() -> receive {tick, _Locn, Value, _Other} -> io:fwrite("~p", [Value]), % print something to show action wait_to_complete() after ?CONTROL_TIMEOUT -> ok end. report(Locn) -> address(Locn) ! report, receive {result, Value, Conf} -> {Locn, Value, Conf} end. % this "ignores" X, Y - assumes ordering as in Cells format({{X, Y}, Value, _Conf}, Text) -> Text ++ io_lib:format("~p", [Value]) ++ decorate(X, Y). decorate(X, Y) when X == 9, Y rem 3 == 0, Y /= 9 -> decorate_x(X) ++ "---------------------\n"; decorate(X, _Y) -> decorate_x(X). decorate_x(9) -> "\n"; decorate_x(X) when X > 0, X rem 3 == 0 -> " | "; decorate_x(_) -> " ". % testing empty(Doubt) -> solve(Doubt, gb_trees:empty()). % format used at http://norvig.com/sudoku.html norvig(List, Doubt) -> Cells = [{X, Y} || Y <- ?RANGE, X <- ?RANGE], Clean = [L || L <- List, (L == $.) or ((L >= $0) and (L =< $9))], Puzzle = lists:foldl(fun set_known/2, gb_trees:empty(), lists:zip(Clean, Cells)), solve(Puzzle, Doubt). set_known({N, _Locn}, Puzzle) when (N < $1) or (N > $9) -> Puzzle; set_known({CharValue, Locn}, Puzzle) -> gb_trees:insert(Locn, list_to_integer([CharValue]), Puzzle). % to run, start "erl" and then enter: % > c(sudoku). % > {Result, Piccy} = sudoku:norvig("4.. ... 8.5" % ".3. ... ..." % "... 7.. ..." % ".2. ... .6." % "... .8. 4.." % "... .1. ..." % "... 6.3 .7." % "5.. 2.. ..." % "1.4 ... ...", 0.1). % ... % > io:fwrite("~s", [Piccy]). % 4 5 1 | 1 4 6 | 8 3 5 % 8 3 7 | 9 2 5 | 7 1 9 % 2 6 9 | 7 8 3 | 6 4 2 % --------------------- % 9 2 4 | 2 9 7 | 3 6 8 % 6 7 3 | 3 8 4 | 4 5 1 % 8 5 1 | 5 1 6 | 9 2 7 % --------------------- % 7 8 9 | 6 1 3 | 5 7 2 % 5 6 2 | 2 9 8 | 1 4 6 % 1 3 4 | 4 7 5 | 3 9 8 % (c) andrew cooke, 2007, released under the gpl. % thanks to j armstrong, the rest of the erlang guys, and p norvig.
From: "andrew cooke" <andrew@...>
Date: Thu, 17 May 2007 08:59:17 -0400 (CLT)
The above was not intended to be efficient - it was motivated largely by a (no doubt naive) desire to explore how very simple negotiation between individuals could find a global solution. As I've mentioned here before, I am reading "Machine Dreams" at the moment, so ideas from economics are constantly at the back of mind. However, the code above is not my initial attempt, but a revision that removed some "premature optimisation". Originally I had a limit to confidence, above which cells would stop sending assertions. The idea was to reduce "useless" messages (since extremely confident cells will not change), but this (wrongly) assumed that assertions help only the cell doing the assertion - in retrospect it seems likely that continued assertions from "fixed" cells help force the change incorrect values. And the "doubt" parameter, while it seems to help, is not clearly justified. My problem is, of course, that I don't understand what is happening in detail. Adding "print" statements gives some guidance, but there are so many messages exchanges that it tends to help fix only bugs in the protocol logic (ie when you can focus on a small set of messages, avoiding the "big picture"). I need a "higher level" way of showing what is happening as the program proceeds. Some kind of statistic (ie a global number plotted against time) or graphic (ie a way of representing the relationship between cells as timeslice images of the grid, or as a movie). But there is a catch-22: it is hard to find a good description without understanding the system. In the comments I think I talk about the result "crystallising out". I think this is a useful metaphor - there is a phase change from a disordered state (random values) to an ordered one (consistent solution). The ordering involves correlations on all scales (not just local neighbours) which, if I remember correctly, is also a property of some phase transitions (I used to have the Landau + Lifshitz book on phase transitions, but seem to have lost it). But how does this carry across in detail? I am starting to believe, for example, that the idea of "competing centres of nucleation" is incorrect. It seems more (or equally?) likely that there is, in some sense, a "single" structure, but that it slowly drifts "out of phase" so that when it meets up later, it becomes inconsistent. I am explaining things poorly. What I mean is that there may be a large group of cells which tend to be self-consistent when you move from one to another along many paths, but which still have some paths that are inconsistent (path being a series of jumps from neighbour to neighbour in the sense of "neighbour" defined in the code). So it would be impossible to identify competing sites for a graphic. Alternatively (perhaps equivalently) the kind of graphic I was thinking of seems to be a simplification of some kind of cluster analysis, with significant clustering at a range of hierarchies. So it's not clear where to select a cutoff, or how useful it would be. Which forces me back to simpler statistics. Two I can think of are: - number of points that agree with the final solution - average of (or any other statistic related to) the number of consistent neighbours a cell has I may modify the code to generate a log file that records cell/value changes - statistics like that could then be assembled from that log without having to re-run the (slow) code. Andrew
From: "andrew cooke" <andrew@...>
Date: Thu, 17 May 2007 09:36:12 -0400 (CLT)
Looking at this problem, comparing the parallel solution with a classic (ie Norvig) constraint propagation/search, you can see support for one the arguments Mirowski makes in Machine Dreams, I think: that the individual is less important than the market. In other words, "top-down" is often more efficient than "bottom-up". This simplifies the issue enormously, of course - and there are no(?) moral issues involved here (cells do not care about democracy). But maybe Sudoku can function for economics in a similar way to chess for searching - as a test case? Is there some way of having a "slider" that can be adjusted to move "intelligence" up and down the system? How much complexity would you need at each "level" to place the intelligence there? What are the levels? Andrew
From: "andrew cooke" <andrew@...>
Date: Thu, 17 May 2007 20:59:59 -0400 (CLT)
Someone just pointed on Reddit that I've screwed up: http://programming.reddit.com/info/1rgar/comments/c1rlgd Here's the correct neighbours function... neighbours({X, Y}) -> CornerX = 3 * ((X - 1) div 3), CornerY = 3 * ((Y - 1) div 3), [{XX, YY} || XX <- [CornerX + P || P <- [1, 2, 3]], YY <- [CornerY + Q || Q <- [1, 2, 3]], {XX, YY} /= {X, Y}] ++ [{XX, Y} || XX <- ?RANGE, XX /= X] ++ [{X, YY} || YY <- ?RANGE, YY /= Y]. Andrew
From: "andrew cooke" <andrew@...>
Date: Thu, 17 May 2007 22:03:20 -0400 (CLT)
About to go to bed, but the latest news is that this no longer appears to converge. The problem is that "my" rules were less local and so "broke up" self-consistent but incorrect blocks. Now, even with a doubt factor of zero, the system is getting stuck in an semi-consistent state. Here are some snapshots: 65217: 467 921 835 231 865 749 958 734 126 823 457 961 719 182 453 645 319 287 492 643 578 576 298 314 184 573 692 66629: 467 921 835 231 865 749 958 734 126 823 457 961 719 382 453 645 319 287 192 643 578 576 298 314 154 578 692 96359: 467 921 835 231 865 749 958 734 126 823 457 961 719 382 453 645 319 287 392 643 578 576 298 314 184 578 692 Where the initial value is age. See how static the system is, even over 30,000 changes in value? I'll leave it running overnight, but don't expect that to terminate. Not sure what to do instead. :o( Andrew
From: "andrew cooke" <andrew@...>
Date: Thu, 17 May 2007 22:38:26 -0400 (CLT)
So, if I don't go running tomorrow morning I can sleep later... I changed the algorithm to "panic" (paranoid?) - now doubt describes the likelihood that, even when a cell wins a contest, it changes its value. So a value near 1 tends to give a cascade of changes. Playing with the value, it seems that the results are quite sensitive when doubt approaches 1 (as you might expect, although the fixed, known values should also dampen things). Currently a value of 0.99 seems to give an apparently useful balance between change and stability. I'll leave this for a while to see how it evolves... Andrew
From: "andrew cooke" <andrew@...>
Date: Thu, 17 May 2007 23:30:13 -0400 (CLT)
There's a bug. I don't know what it is, but there is one - at least one non-certain cell is stuck at a fixed value. Andrew
From: "andrew cooke" <andrew@...>
Date: Fri, 18 May 2007 08:03:23 -0400 (CLT)
During the day I need to do real work(!), so I'll leave this running. It seems to be making a decent exploration of the available space. Note that the interface between cells and control is becoming more modular (sleep, status and report messages) as I modify the code to make it easier to understand what is happening. Output to the log is analysed by separate tools not shown here. Typical output to the screen at the moment looks like: =INFO REPORT==== 18-May-2007::08:02:24 === status at 150000 472 361 895 635 849 712 851 725 346 428 937 561 913 586 427 756 412 983 589 653 274 567 294 138 124 978 659 % a parallel sudoku solver. each cell on the grid is a separate % process, aware of - and negotiating with - its "neighbours" (those % cells it must not conflict with). % a cell is very simple - it contains just two integer values as % "mutable" state: the current digit and a "confidence" in that value. % the negotiation protocol is defined below. cells "ping" the % controller when the exchange results in a change of value. the % controller stops the system when there has been no change in any % value for CONTROL_TIMEOUT (currently 1s). % this is terribly, terribly brain-dead and inefficient (takes several % - sometimes tens of - minutes to solve the test case on my newish % linux box). tuning the doubt parameter may help slightly (a value % of 0.1 seem to work). % i think some very interesting graphics could be generated from this % code - think of the solution as a kind of crystallisation, with % competing centres of nucleation. please contact me % (andrew@...) if interested... -module(sudoku). -export([solve/2, empty/2, starting/3, norvig/0, norvig/2, norvig/3]). -export([neighbours/1]). -define(CELL_TIMEOUT, 1). % see comments below -define(CONTROL_TIMEOUT, 1000). % shutdown if no changes after this -define(RANGE, lists:seq(1, 9)). % [1 ... 9] -define(NULL, "/dev/null"). -define(DOUBT, panic). % doubt algorithm -record(cell, {locn, nbrs, dbt, send=0, recv=0, chng=0}). % negotiation between cells is described below but first we need to % get everything up and running. solve(Puzzle, Doubt) -> solve(Puzzle, Doubt, ?NULL). solve(Puzzle, Doubt, File) -> {ok, Log} = file:open(File, [write]), Cells = [{X, Y} || Y <- ?RANGE, X <- ?RANGE], application:start(sasl), % debug info for failed processes register(ctrl, self()), [new_cell(Doubt, Puzzle, Cell, Log) || Cell <- Cells], broadcast(Cells, start), ok = wait_to_complete(Cells, Log, 0), broadcast(Cells, sleep), Result = result(Cells), broadcast(Cells, stop), Result. broadcast(Cells, Message) -> [address(Cell) ! Message || Cell <- Cells]. % for easy identification we use the string {X,Y}, converted to an % atom, as the name for each process. address(Locn) -> list_to_atom(lists:flatten(io_lib:format("~p", [Locn]))). new_cell(Doubt, Puzzle, Locn, Log) -> {Value, Confidence} = initialise(Puzzle, Locn), io:fwrite(Log, "~p.~n", [{Locn, Value, Confidence}]), Cell = #cell{locn = Locn, nbrs = named_neighbours(Locn), dbt = Doubt}, % create the process and register its name register(address(Locn), proc_lib:spawn(sudoku, starting, [Cell, Value, Confidence])). % a puzzle is represented as a map (implemented in gb_trees) from the % location to the value. only cells with initial values appear in the % map. initialise(Puzzle, Locn) -> case gb_trees:is_defined(Locn, Puzzle) of true -> {gb_trees:get(Locn, Puzzle), certain}; false -> {random:uniform(9), 0} end. named_neighbours(Locn) -> [address(N) || N <- neighbours(Locn)]. % cells that can potentially conflict with this cell neighbours({X, Y}) -> CornerX = 3 * ((X - 1) div 3), CornerY = 3 * ((Y - 1) div 3), lists:usort([{XX, YY} || XX <- [CornerX + P || P <- [1, 2, 3]], YY <- [CornerY + Q || Q <- [1, 2, 3]], {XX, YY} /= {X, Y}] ++ [{XX, Y} || XX <- ?RANGE, XX /= X] ++ [{X, YY} || YY <- ?RANGE, YY /= Y]). % each process is a simple state machine. this is the starting state, % waiting on an initial message (so that all the processes can be % deployed before they try talking to each other). starting(Cell, Value, Confidence) -> receive start -> searching(Cell, Value, Confidence) end. % quiescent state, allowing for clean shutdown and consistent reporting. sleeping(Cell, Value, Confidence) -> receive stop -> ok ; report -> report(Cell, Value, Confidence), sleeping(Cell, Value, Confidence); status -> status(Cell, Value, Confidence), sleeping(Cell, Value, Confidence); awake -> searching(Cell, Value, Confidence) end. % the intermediate state (repeated) searching(Cell, Value, Confidence) -> receive sleep -> sleeping(Cell, Value, Confidence); report -> report(Cell, Value, Confidence), searching(Cell, Value, Confidence); status -> status(Cell, Value, Confidence), searching(Cell, Value, Confidence); Message -> negotiate(Cell, Value, Confidence, Message) after ?CELL_TIMEOUT -> assert(Cell, Value, Confidence) end. % next is our negotiate table for the negotiation protocol (the % protocol is symmetric - our neighbour has the same table). for each % message we reply if necessary (we either deny or yield to an % assertion) and then update our internal state (Value and % Confidence). % note that we *never* change the Value if we agree with our % neighbour. this ensures that the process is stationary (if we have % a solution we don't mess it up) which, together with a quasi-random % exploration of the available parameter space, is pretty much our % only (weak!) guarantee for convergence. % the Confidence value (together with the "doubt" parameter for the % run) is an optimisation to help guide us to a solution more quickly. % if we are certain, and our neighbour is in conflict, deny them. negotiate(Cell, Value, certain, {assert, From, Value, _Test}) -> Cell2 = deny(Cell, From, Value), searching(Cell2, Value, certain); % if our neighbour is certain, and we are conflict, change. negotiate(Cell, Value, _Confidence, {assert, From, Value, certain}) -> Cell2 = yield(Cell, From, Value), new_value(Cell2, Value); % otherwise, conflict is resolved according to relevant confidence % levels. in this case, the neighbour is more confident than we are, % so we yield and change. negotiate(Cell, Value, Confidence, {assert, From, Value, Test}) when Confidence < Test -> Cell2 = yield(Cell, From, Value), new_value(Cell2, Value); % alternatively, if we are more confident, we deny them the value. % this is where doubt comes into play.... negotiate(Cell, Value, Confidence, {assert, From, Value, _Test}) -> Cell2 = deny(Cell, From, Value), doubt(Cell2, Value, Confidence); % if our neighbour claims a different value from our own then we agree % - this should also increase our confidence slightly. negotiate(Cell, Value, Confidence, {assert, From, Other, _Test}) -> Cell2 = yield(Cell, From, Other), searching(Cell2, Value, confirm(Confidence)); % if our neighbour has denied us a value (they were more certain than % us on the receipt of our assertion), then we must change... negotiate(Cell, Value, _Confidence, {deny, Value}) -> new_value(Cell, Value); % ...but not if we already changed and no longer have the value we % asserted! negotiate(Cell, Value, Confidence, {deny, _Other}) -> searching(Cell, Value, Confidence); % if our neighbour acquiesces with our assertion we can increase our % confidence a little... negotiate(Cell, Value, Confidence, {yield, Value}) -> searching(Cell, Value, confirm(Confidence)); % ...but if they agreed with a value we no longer have then we can do % little about it. negotiate(Cell, Value, Confidence, {yield, _Other}) -> searching(Cell, Value, Confidence). % the various actions used above deny(Cell, From, Value) -> From ! {deny, Value}, Cell#cell{recv = Cell#cell.recv + 1}. yield(Cell, From, Value) -> From ! {yield, Value}, Cell#cell{recv = Cell#cell.recv + 1}. assert(Cell, Value, Confidence) -> random_nbr(Cell) ! {assert, self(), Value, Confidence}, searching(Cell#cell{send = Cell#cell.send + 1}, Value, Confidence). random_nbr(Cell) -> lists:nth(random:uniform(length(Cell#cell.nbrs)), Cell#cell.nbrs). new_value(Cell, Old) -> case random:uniform(9) of Old -> new_value(Cell, Old); New -> ctrl ! {tick, Cell#cell.locn, Old, New}, searching(Cell#cell{chng = Cell#cell.chng + 1}, New, 0) end. doubt(Cell, Value, certain) -> searching(Cell, Value, certain); doubt(Cell, Value, Confidence) -> case ?DOUBT of panic -> case random:uniform() > Cell#cell.dbt of true -> searching(Cell, Value, Confidence); false -> new_value(Cell, Value) end; doubt -> searching(Cell, Value, lists:max([0, trunc(Confidence * Cell#cell.dbt)])) end. confirm(certain) -> certain; confirm(Confidence) -> Confidence + 1. report(_Cell, Value, Confidence) -> ctrl ! {result, Value, Confidence}. status(Cell, Value, Confidence) -> error_logger:info_msg("~p = ~p (~p) send:~p recv:~p chng:~p", [Cell#cell.locn, Value, Confidence, Cell#cell.send, Cell#cell.recv, Cell#cell.chng]). % shutdown and reporting wait_to_complete(Cells, Log, Count) when Count rem 10000 == 0 -> % broadcast(Cells, status), {_Values, Puzzle} = result(Cells), error_logger:info_msg("status at ~p~n~n~s~n", [Count, ascii(Puzzle)]), wait_for_tick(Cells, Log, Count); wait_to_complete(Cells, Log, Count) -> wait_for_tick(Cells, Log, Count). wait_for_tick(Cells, Log, Count) -> receive {tick, Locn, _Old, New} -> io:fwrite(Log, "~p.~n", [{Locn, New}]), wait_to_complete(Cells, Log, Count + 1) after ?CONTROL_TIMEOUT -> file:close(Log), ok end. result(Cells) -> Values = [], Puzzle = gb_trees:empty(), collect_reports(Values, Puzzle, Cells). collect_reports(Values, Puzzle, []) -> {Values, Puzzle}; collect_reports(Values, Puzzle, [Cell|Cells]) -> address(Cell) ! report, receive {result, Value, Confidence} -> collect_reports([{Cell, Value, Confidence}|Values], gb_trees:insert(Cell, Value, Puzzle), Cells) end. % formatting value(Puzzle, Locn) -> integer_to_list(gb_trees:get(Locn, Puzzle)). values(N, Puzzle, X, Y) -> lists:flatten([value(Puzzle, {XX,Y}) || XX <- lists:seq(X, X + N - 1)]). line(Puzzle, Y) -> util:join_with([values(3, Puzzle, X, Y) || X <- [1, 4, 7]], " "). lines(N, Puzzle, Y) -> util:join_with([line(Puzzle, YY) || YY <- lists:seq(Y, Y + N - 1)], "\n") ++ "\n". ascii(Puzzle) -> util:join_with([lines(3, Puzzle, Y) || Y <- [1, 4, 7]], lists:duplicate(11, $\ ) ++ "\n"). % testing empty(Doubt, File) -> solve(gb_trees:empty(), Doubt, File). % format used at http://norvig.com/sudoku.html norvig(List, Doubt) -> norvig(List, Doubt, ?NULL). norvig(List, Doubt, File) -> Cells = [{X, Y} || Y <- ?RANGE, X <- ?RANGE], Clean = [L || L <- List, (L == $.) or ((L >= $0) and (L =< $9))], Puzzle = lists:foldl(fun set_known/2, gb_trees:empty(), lists:zip(Clean, Cells)), solve(Puzzle, Doubt, File). set_known({N, _Locn}, Puzzle) when (N < $1) or (N > $9) -> Puzzle; set_known({CharValue, Locn}, Puzzle) -> gb_trees:insert(Locn, list_to_integer([CharValue]), Puzzle). norvig() -> norvig("4.. ... 8.5" ".3. ... ..." "... 7.. ..." ".2. ... .6." "... .8. 4.." "... .1. ..." "... 6.3 .7." "5.. 2.. ..." "1.4 ... ...", 0.1, "example-0-1.txt"). % c(sudoku), {R,P} = sudoku:norvig().
From: "andrew cooke" <andrew@...>
Date: Fri, 18 May 2007 20:51:18 -0400 (CLT)
I gave up on the pure "1-on-1" negotiation approach. It's simply too inefficient - I ran 8450000 exchanges during the day without finding a solution. Instead, I've modified the algorithm so that the cells "collaborate" for the greater good of their "community". The result just converged in just 148 exchanges: 457 321 865 836 548 497 192 796 213 927 256 163 618 387 452 435 419 789 287 653 873 539 248 215 164 197 694 I'll post more on a new thread once I've cleaned up the code. Andrew