Guest User

Full NimConstraint

a guest
Dec 22nd, 2019
97
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Nim 7.42 KB | None | 0 0
  1. import tables, sequtils, sugar, macros, sets, heapqueue, strutils, ittree
  2.  
  3. ## nimconstraint: A CSP-Solver written in Nim
  4. ##      Author: Timothy Hornick @Xydium
  5. ##      Date: 20 December, 2019
  6.  
  7. type
  8.     Variable[T] = ref object
  9.         ## Represents a Node in the CSP Graph
  10.         name: string
  11.             ## Unique name of the Variable
  12.             ## TODO: Currently user-enforced
  13.         domain: seq[T]
  14.             ## Domain of the variable as a
  15.             ## finite, ordered set
  16.         value: T
  17.             ## Current value of the variable,
  18.             ## used to represent an assignment
  19.    
  20.     Constraint*[T] = ref object
  21.         ## A function that subsets valid combinations
  22.         ## of values for its variables. Represents
  23.         ## an Arc or Multi-Arc in the CSP Graph
  24.         variables: seq[Variable[T]]
  25.             ## The list of Variable objects checked
  26.         predicate: proc(x: varargs[T]): bool
  27.             ## The function computing satisfaction
  28.  
  29.     Problem*[T] = ref object
  30.         ## A CSP Graph with well-defined solving algorithms
  31.         variables: TableRef[string, Variable[T]]
  32.             ## Links the Names of Nodes to their Reference
  33.         constraints: TableRef[string, seq[Constraint[T]]]
  34.             ## Links the Names of Nodes to their Constraints
  35.  
  36. proc newProblem*[T](): Problem[T] =
  37.     ## Returns an empty CSP Graph
  38.     result = Problem[T](
  39.         variables: newTable[string, Variable[T]](),
  40.         constraints: newTable[string, seq[Constraint[T]]]()
  41.     )
  42.  
  43. proc addVariable*[T](problem: Problem[T], name: string, domain: seq[T]) =
  44.     ## Adds a named Node to the CSP Graph with the given domain
  45.     let v = Variable[T](
  46.         name: name,
  47.         domain: domain
  48.     )
  49.  
  50.     problem.variables.add(name, v)
  51.  
  52. proc addConstraint*[T](problem: Problem[T], predicate: (x: varargs[T]) -> bool,
  53.     variables: varargs[string]) =
  54.     ## Adds an N-Ary Constraint to the CSP Graph based on
  55.     ## the number of Variable names supplied
  56.  
  57.     # Create the Constraint with direct Variable references
  58.     let constraint = Constraint[T](
  59.         variables: variables.map(s => problem.variables[s]),
  60.         predicate: predicate
  61.     )
  62.  
  63.     # Add the Constraint to the adjacency table
  64.     for variable in constraint.variables:
  65.         # Add a seq for the variable if needed
  66.         discard problem.constraints.hasKeyOrPut(
  67.             variable.name, newSeq[Constraint[T]]())
  68.  
  69.         problem.constraints[variable.name].add(constraint)
  70.  
  71. proc isUnary[T](constraint: Constraint[T]): bool =
  72.     ## Returns whether the Constraint is Unary
  73.     constraint.variables.len == 1
  74.  
  75. proc isBinary[T](constraint: Constraint[T]): bool =
  76.     ## Returns whether the Constraint is Binary
  77.     constraint.variables.len == 2
  78.  
  79. proc backtrack[T](problem: Problem[T], unassigned: seq[Variable[T]],
  80.     assignment: TableRef[Variable[T], T]): bool =
  81.  
  82.     if unassigned.isEmpty:
  83.         return # Assignment is Complete
  84.  
  85.     ## Select a variable
  86.     let current = unassigned[0]
  87.  
  88.     ## Assign a variable
  89.     for value in current.domain:
  90.         assignment[current] = value
  91.  
  92.         ## Arc consistency -- preserve original domain
  93.             ## Inconsistent? Continue
  94.         ## N-ary consistency
  95.             ## Inconsistent? Continue
  96.         ## All clear?
  97.  
  98.         if problem.backtrack(unassigned[1..high(unassigned)], assignment):
  99.             return true
  100.  
  101.  
  102.     ## Perform arc consistency to check early inconsistent state
  103.     ## Check any non-binary constraints involving the variable
  104.     ## All clear? Recurse to next variable
  105.     ## Not clear?
  106.  
  107. proc nodeConsistent[T](problem: Problem[T]): bool =
  108.     ## Applies Node Consistency to all Nodes in the
  109.     ## CSP Graph, subsetting domains for each constraint
  110.     ##
  111.     ## Return: Whether the Problem is Node Consistent
  112.     for node in problem.variables.values:
  113.         for constraint in problem.constraints[node.name]:
  114.             # Node Consistency for Unary Constraints only
  115.             if not constraint.isUnary: continue
  116.  
  117.             # There may be a faster way than constructing
  118.             # a new seq, but as a 1-time step, not important.
  119.             # Removing values is expensive, but is it more
  120.             # expensive than constructing seqs
  121.             var ncdomain = newSeq[T]()
  122.  
  123.             # Add the values which satisfy the unary constraint
  124.             for value in node.domain:
  125.                 if constraint.predicate(value):
  126.                     ncdomain.add(value)
  127.            
  128.             # Empty domain? Node is Inconsistent
  129.             if ncdomain.len == 0:
  130.                 return false
  131.  
  132.             # Pass along the domain to the next constraint
  133.             node.domain = ncdomain
  134.    
  135.     return true
  136.  
  137. proc search*[T](problem: Problem[T]): seq[tuple[name: string, value: T]] =
  138.     ## Node Consistency
  139.     discard
  140.  
  141. macro define*[T](problem: Problem[T], input: untyped): untyped =
  142.     ## TODO: Remove looping/list
  143.     result = newStmtList()
  144.  
  145.     for node in input:
  146.         case node.kind:
  147.         of nnkInfix:
  148.             let name = node[1].strVal
  149.             let domain = node[2]
  150.  
  151.             result.add quote do:
  152.                 `problem`.addVariable(`name`, `domain`)
  153.         else:
  154.             echo "Cannot Instantiate Variable without Infix Structure"
  155.             echo node.treeRepr
  156.  
  157. proc parseVars(node: NimNode): (seq[string], NimNode) =
  158.     ## Traverses the AST to find any identifiers
  159.     ## and either replaces them with a constraint
  160.     ## vararg access, or treats them as a Nim
  161.     ## identifier if prefixed `u` or not alphanumeric.
  162.     ##
  163.     ## Return: The Variable names in AST First Occurence Order
  164.     var variables = newTable[string, int]()
  165.     var varOrder = newSeq[string]()
  166.  
  167.     proc inspect(node: NimNode): NimNode =
  168.         result = node.copyNimNode
  169.  
  170.         for current in node:
  171.             if current.kind == nnkIdent:
  172.                 if current.strVal[0] == 'u':
  173.                     ## If tagged as a literal, keep it, but drop tag
  174.                     result.add ident(current.strVal.substr(1))
  175.                 elif current.strVal.isAlphaNumeric:
  176.                     ## Any untagged identifier is assumed a Variable
  177.                     ## Track its order in the AST
  178.                     if not variables.hasKeyOrPut(current.strVal, variables.len):
  179.                         varOrder.add(current.strVal)
  180.  
  181.                     ## Then replace the Identifier with an Array Access
  182.                     ## corresponding to its location in the constraint
  183.                     result.add newNimNode(nnkBracketExpr).add(
  184.                         ident("x"),
  185.                         newIntLitNode(variables[current.strVal])
  186.                     )
  187.                 else:
  188.                     result.add current
  189.             else:
  190.                 result.add inspect(current)
  191.    
  192.     return (varOrder, inspect(node))
  193.  
  194. macro constrain*[T](problem: Problem[T], input: untyped): untyped =
  195.     var stmts = newStmtList()
  196.  
  197.     for i in 0..<input.len:
  198.         let (vars, tree) = parseVars(input[i])
  199.  
  200.         let x = ident"x"
  201.  
  202.         var constcall = quote do: addConstraint(
  203.             `problem`,
  204.             proc(`x`: untyped): bool = `tree`,
  205.             `vars`)
  206.  
  207.         constcall[2][3][1][0] = ident"x"
  208.  
  209.         echo constcall.treeRepr
  210.  
  211.         stmts.add constcall
  212.  
  213.     return stmts
  214.  
  215. let test = newProblem[int]()
  216.  
  217. test.define:
  218.     X in toSeq(0..9)
  219.     Y in toSeq(0..9)
  220.  
  221. test.constrain:
  222.     Y == X * X
Advertisement
Add Comment
Please, Sign In to add comment