ayaderaghul

asynchorous backtracking algorithm - 4 queens

Jul 8th, 2026 (edited)
372
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
TypeScript 10.36 KB | Science | 0 0
  1. type AgentID = number;
  2. type Value = number;
  3.  
  4. export interface Assignment {
  5.     agentID: AgentID;
  6.     value: Value;
  7. }
  8.  
  9. export type Nogood = Assignment[];
  10.  
  11. export type Message =
  12.     | { type: 'OK'; from: AgentID; to: AgentID; payload: Value }
  13.     | { type: 'NOGOOD'; from: AgentID; to: AgentID; payload: Nogood }
  14.     | { type: 'ADD_NEIGHBOR'; from: AgentID; to: AgentID; payload: null };
  15.  
  16. export type ConstraintFunction = (a1: AgentID, v1: Value, a2: AgentID, v2: Value) => boolean;
  17.  
  18.  
  19. export class Agent {
  20.     public id: AgentID;
  21.     public domain: Value[];
  22.     public neighbors: Set<AgentID>;
  23.     public environment: Environment;
  24.     public constraint: ConstraintFunction;
  25.  
  26.     public currentValue: Value;
  27.     public agentView: Map<AgentID, Value> = new Map();
  28.     public nogoodList: Nogood[] = [];
  29.     public inbox: Message[] = [];
  30.  
  31.     constructor(
  32.         id: AgentID,
  33.         domain: Value[],
  34.         neighbors: AgentID[],
  35.         environment: Environment,
  36.         constraint: ConstraintFunction
  37.     ) {
  38.         this.id = id;
  39.         this.domain = [...domain];
  40.         this.neighbors = new Set(neighbors);
  41.         this.environment = environment;
  42.         this.constraint = constraint;
  43.         this.currentValue = this.domain[0];
  44.     }
  45.  
  46.     public enqueue(msg: Message) {
  47.         this.inbox.push(msg);
  48.     }
  49.  
  50.     public initialize() {
  51.         this.notifyNeighbors();
  52.     }
  53.  
  54.     public handleMessage(msg: Message) {
  55.         if (!this.environment.running) return;
  56.        
  57.  
  58.         switch (msg.type) {
  59.             case 'OK':
  60.                 this.agentView.set(msg.from, msg.payload);
  61.                 this.cleanupNogoods(msg.from, msg.payload);
  62.                 this.checkAgentView();
  63.                 break;
  64.             case 'NOGOOD':
  65.                 this.handleNogood(msg.payload, msg.from);
  66.                 break;
  67.             case 'ADD_NEIGHBOR':
  68.                 this.neighbors.add(msg.from);
  69.                 this.environment.sendMessage('OK', this.id, msg.from, this.currentValue);
  70.                 break;
  71.         }
  72.     }
  73.  
  74.     private cleanupNogoods(changedAgent: AgentID, newValue: Value) {
  75.         this.nogoodList = this.nogoodList.filter(nogood => {
  76.             const relevant = nogood.find(a => a.agentID === changedAgent);
  77.             return !relevant || relevant.value === newValue;
  78.         });
  79.     }
  80.  
  81.     private handleNogood(nogood: Nogood, from: AgentID) {
  82.         this.nogoodList.push(nogood);
  83.  
  84.         for (const assignment of nogood) {
  85.             if (assignment.agentID !== this.id && !this.agentView.has(assignment.agentID)) {
  86.                 this.agentView.set(assignment.agentID, assignment.value);
  87.                 if (!this.neighbors.has(assignment.agentID)) {
  88.                     this.neighbors.add(assignment.agentID);
  89.                     this.environment.sendMessage('ADD_NEIGHBOR', this.id, assignment.agentID, null);
  90.                 }
  91.             }
  92.         }
  93.  
  94.         this.checkAgentView();
  95.  
  96.         // The sender deleted its own view of us right before sending this
  97.         // nogood (see backtrack()) and is now blind to our value until we
  98.         // tell it. We must reply unconditionally, whether or not our value
  99.         // actually changed above — if we were already consistent and stayed
  100.         // put, the sender would otherwise wait forever for an update.
  101.         this.environment.sendMessage('OK', this.id, from, this.currentValue);
  102.     }
  103.  
  104.     private checkAgentView() {
  105.         if (!this.isConsistent(this.currentValue)) {
  106.             const candidate = this.domain.find(d => this.isConsistent(d));
  107.             if (candidate !== undefined) {
  108.                 this.currentValue = candidate;
  109.                
  110.                 this.notifyNeighbors();
  111.             } else {
  112.                 this.backtrack();
  113.             }
  114.         }
  115.     }
  116.  
  117.     private isNogoodCompatibleWithView(nogood: Nogood): boolean {
  118.         for (const assignment of nogood) {
  119.             if (assignment.agentID === this.id) continue;
  120.             if (this.agentView.has(assignment.agentID)) {
  121.                 if (this.agentView.get(assignment.agentID) !== assignment.value) {
  122.                     return false;
  123.                 }
  124.             }
  125.         }
  126.         return true;
  127.     }
  128.  
  129.     private isConsistent(val: Value): boolean {
  130.         for (const [otherId, otherVal] of this.agentView) {
  131.             if (otherId < this.id) {
  132.                 if (!this.constraint(this.id, val, otherId, otherVal)) return false;
  133.             }
  134.         }
  135.  
  136.         for (const nogood of this.nogoodList) {
  137.             if (this.isNogoodCompatibleWithView(nogood)) {
  138.                 const forbidsMyValue = nogood.some(a =>
  139.                     a.agentID === this.id && a.value === val
  140.                 );
  141.                 if (forbidsMyValue) return false;
  142.             }
  143.         }
  144.  
  145.         return true;
  146.     }
  147.  
  148.     private backtrack() {
  149.         // Combine reasons across the WHOLE domain, not just currentValue.
  150.         // "currentValue fails because of X" only tells you one value is bad;
  151.         // to justify giving up you need every value in the domain to be
  152.         // accounted for. getReasonForFailure already strips self-referencing
  153.         // entries, so this map only ever collects genuine external causes.
  154.         const combinedNogoodMap = new Map<AgentID, Assignment>();
  155.  
  156.         for (const d of this.domain) {
  157.             const reason = this.getReasonForFailure(d);
  158.             if (reason) {
  159.                 reason.forEach(a => combinedNogoodMap.set(a.agentID, a));
  160.             }
  161.         }
  162.  
  163.         let newNogood = Array.from(combinedNogoodMap.values())
  164.             .filter(a => a.agentID < this.id);
  165.  
  166.         if (newNogood.length === 0) {
  167.             this.environment.broadcastTermination("No Solution Exists");
  168.         } else {
  169.             const target = newNogood.reduce((prev, curr) =>
  170.                 (curr.agentID > prev.agentID ? curr : prev)
  171.             );
  172.  
  173.            
  174.  
  175.             this.environment.sendMessage('NOGOOD', this.id, target.agentID, newNogood);
  176.             this.agentView.delete(target.agentID);
  177.  
  178.             this.nogoodList = this.nogoodList.filter(ng =>
  179.                 !ng.some(a => a.agentID === target.agentID)
  180.             );
  181.         }
  182.     }
  183.  
  184.     private getReasonForFailure(val: Value): Assignment[] | null {
  185.         for (const [otherId, otherVal] of this.agentView) {
  186.             if (!this.constraint(this.id, val, otherId, otherVal)) {
  187.                 return [{ agentID: otherId, value: otherVal }];
  188.             }
  189.         }
  190.         for (const nogood of this.nogoodList) {
  191.             if (this.isNogoodCompatibleWithView(nogood) &&
  192.                 nogood.some(a => a.agentID === this.id && a.value === val)) {
  193.                 return nogood.filter(a => a.agentID !== this.id);
  194.             }
  195.         }
  196.         return null;
  197.     }
  198.  
  199.     private notifyNeighbors() {
  200.         for (const neighborId of this.neighbors) {
  201.             if (neighborId > this.id) {
  202.                 this.environment.sendMessage('OK', this.id, neighborId, this.currentValue);
  203.             }
  204.         }
  205.     }
  206. }
  207.  
  208. export class Environment {
  209.     private agents: Map<AgentID, Agent> = new Map();
  210.     public running: boolean = true;
  211.  
  212.     addAgent(agent: Agent) { this.agents.set(agent.id, agent); }
  213.  
  214.     sendMessage<T extends Message['type']>(
  215.         type: T,
  216.         from: AgentID,
  217.         to: AgentID,
  218.         payload: Extract<Message, { type: T }>['payload']
  219.     ) {
  220.         const msg = { type, from, to, payload } as Message;
  221.         this.agents.get(to)?.enqueue(msg);
  222.     }
  223.  
  224.     broadcastTermination(reason: string) {
  225.         console.log(`[Termination] ${reason}`);
  226.         this.running = false;
  227.     }
  228.  
  229.     private hasPendingMessages(): boolean {
  230.         for (const agent of this.agents.values()) {
  231.             if (agent.inbox.length > 0) return true;
  232.         }
  233.         return false;
  234.     }
  235.  
  236.     public process(maxSteps: number = 200000) {
  237.         console.log("Starting ABT for N-Queens...");
  238.         this.agents.forEach(a => a.initialize());
  239.  
  240.         let steps = 0;
  241.         while (this.running && this.hasPendingMessages()) {
  242.             if (steps++ >= maxSteps) {
  243.                 console.log("Safety limit reached - possible infinite loop.");
  244.                 this.running = false;
  245.                 return;
  246.             }
  247.             for (const agent of this.agents.values()) {
  248.                 if (!this.running) break;
  249.                 const msg = agent.inbox.shift();
  250.                 if (msg) agent.handleMessage(msg);
  251.             }
  252.         }
  253.  
  254.         if (this.running) {
  255.             this.printSolution();
  256.         }
  257.         console.log(`Total steps: ${steps}`);
  258.     }
  259.  
  260.     private printSolution() {
  261.         console.log("\n=== SOLUTION FOUND ===");
  262.         const solution = Array.from(this.agents.values())
  263.             .sort((a, b) => a.id - b.id)
  264.             .map(a => `Row ${a.id} -> Column ${a.currentValue}`);
  265.  
  266.         console.log(solution.join('\n'));
  267.  
  268.         const positions = Array.from(this.agents.values())
  269.             .sort((a,b) => a.id - b.id)
  270.             .map(a => a.currentValue);
  271.         if (this.isValidQueensSolution(positions)) {
  272.             console.log("Valid N-Queens solution!");
  273.         } else {
  274.             console.log("Solution found but failed validation.");
  275.             // dump full state of every agent for debugging
  276.             for (const agent of Array.from(this.agents.values()).sort((a,b)=>a.id-b.id)) {
  277.                 console.log(`  A${agent.id} val=${agent.currentValue} view=${JSON.stringify([...agent.agentView])} nogoods=${JSON.stringify(agent.nogoodList)}`);
  278.             }
  279.         }
  280.     }
  281.  
  282.     private isValidQueensSolution(pos: number[]): boolean {
  283.         for (let i = 0; i < pos.length; i++) {
  284.             for (let j = i + 1; j < pos.length; j++) {
  285.                 if (pos[i] === pos[j] || Math.abs(i - j) === Math.abs(pos[i] - pos[j])) {
  286.                     return false;
  287.                 }
  288.             }
  289.         }
  290.         return true;
  291.     }
  292. }
  293.  
  294. const N = 10;
  295. const env = new Environment();
  296. const domain = Array.from({length: N}, (_, i) => i);
  297.  
  298. const queenConstraint: ConstraintFunction = (r1, c1, r2, c2) =>
  299.     c1 !== c2 && Math.abs(r1 - r2) !== Math.abs(c1 - c2);
  300.  
  301. for (let i = 0; i < N; i++) {
  302.     const neighbors = Array.from({length: N - i - 1}, (_, k) => i + 1 + k);
  303.     const agent = new Agent(i, domain, neighbors, env, queenConstraint);
  304.     env.addAgent(agent);
  305. }
  306.  
  307. env.process();
Advertisement
Add Comment
Please, Sign In to add comment