1010
1111class FindC2 (FindCBase ):
1212 """
13- This is the version of the FindC function that was presented in
14- Bessiere, Christian, et al., "Learning constraints through partial queries", AIJ 2023
15-
13+ Implementation of the FindC algorithm from Bessiere et al., "Learning constraints through partial queries" (AIJ 2023).
14+
1615 This function works also for non-normalised target networks!
1716 """
1817
@@ -32,7 +31,8 @@ def findscope(self):
3231 """
3332 Get the findscope function to be used.
3433
35- :return: The findscope function.
34+ Returns:
35+ callable: The function used to determine constraint scopes
3636 """
3737 return self ._findscope
3838
@@ -41,32 +41,39 @@ def findscope(self, findscope):
4141 """
4242 Set the findscope function to be used.
4343
44- :param findscope: The findscope function.
44+ Args:
45+ findscope (callable): The function to be used for determining constraint scopes
4546 """
4647 self ._findscope = findscope
4748
4849 def run (self , scope ):
4950 """
50- Run the FindC2 algorithm.
51+ Execute the FindC2 algorithm to learn constraints within a given scope.
52+
53+ Args:
54+ scope (list): Variables defining the scope in which to search for constraints
5155
52- :param scope: The scope in which we search for a constraint.
53- :return: The constraint found.
56+ Returns:
57+ list: The constraint(s) found in the given scope.
58+
59+ Raises:
60+ Exception: If the target constraint is not in the bias (search space).
5461 """
5562 assert self .ca is not None
5663
5764 scope_values = [x .value () for x in scope ]
5865
59- # Initialize delta
66+ # Initialize delta with constraints from bias that match the scope
6067 delta = get_con_subset (self .ca .instance .bias , scope )
6168 kappaD = [c for c in delta if check_value (c ) is False ]
69+ # Join the constraints in delta with the violated constraints in kappaD
6270 delta = join_con_net (delta , kappaD )
6371
64- # We need to take into account only the constraints in the scope we search on
72+ # Get subset of learned constraints in the current scope
6573 sub_cl = get_con_subset (self .ca .instance .cl , scope )
6674
6775 while True :
68-
69- # Try to generate a counter example to reduce the candidates
76+ # Generate a query to distinguish between candidate constraints
7077 if self .generate_findc_query (sub_cl , delta ) is False :
7178
7279 # If no example could be generated
@@ -76,7 +83,7 @@ def run(self, scope):
7683
7784 restore_scope_values (scope , scope_values )
7885
79- # Unravel delta nested ands
86+ # Unravel nested AND constraints
8087 delta_unraveled = []
8188 for c in delta :
8289 if c .name == 'and' :
@@ -87,8 +94,7 @@ def run(self, scope):
8794 else :
8895 delta_unraveled .append ([c ])
8996
90- # Return random c in delta otherwise (if more than one, they are equivalent w.r.t. C_l)
91- # Choose the constraint with the smallest number of conjunctions
97+ # Return the smallest equivalent conjunction (if more than one, they are equivalent w.r.t. C_l)
9298 delta_unraveled = sorted (delta_unraveled , key = lambda x : len (x ))
9399 return delta_unraveled [0 ]
94100
@@ -103,10 +109,10 @@ def run(self, scope):
103109 # delta <- joint(delta,K_{delta}(e))
104110
105111 kappaD = [c for c in delta if check_value (c ) is False ]
106-
107112 scope2 = self .ca .run_find_scope (list (scope ))
108113
109114 if len (scope2 ) < len (scope ):
115+ # Recursively learn constraint in sub-scope
110116 c = self .run (scope2 )
111117 self .ca .add_to_cl (c )
112118 sub_cl .append (c )
@@ -115,25 +121,30 @@ def run(self, scope):
115121
116122 def generate_findc_query (self , L , delta ):
117123 """
118- Changes directly the values of the variables
124+ Generate a query that helps distinguish between candidate constraints.
119125
120- :param L: learned network in the given scope
121- :param delta: candidate constraints in the given scope
122- :return: Boolean value representing a success or failure on the generation
123- """
126+ Args:
127+ L (list): Currently learned constraints in the scope
128+ delta (list): Candidate constraints to distinguish between
129+
130+ Returns:
131+ bool: True if a query was generated successfully, False otherwise
124132
133+ Note:
134+ The method directly modifies variable values in the constraint network
135+ """
125136 tmp = cp .Model (L )
126137
127138 satisfied_delta = sum ([c for c in delta ]) # get the amount of satisfied constraints from B
128139
129140 scope = get_scope (delta [0 ])
141+
130142 # at least 1 violated and at least 1 satisfied
131143 # we want this to assure that each answer of the user will reduce
132144 # the set of candidates
133145 tmp += satisfied_delta < len (delta )
134146 tmp += satisfied_delta > 0
135147
136-
137148 max_conj_size = get_max_conjunction_size (delta )
138149 delta_p = get_delta_p (delta )
139150
@@ -143,23 +154,24 @@ def generate_findc_query(self, L, delta):
143154 kappa_delta_p = sum ([c for c in delta_p [p ]])
144155 s += kappa_delta_p < len (delta_p [p ])
145156
146-
147- if not s .solve (): # if a solution is found
157+ # Solve without objective for start
158+ if not s .solve (): # if a solution is not found
148159 continue
149160
150161 # Next solve will change the values of the variables in lY
151162 # so we need to return them to the original ones to continue if we don't find a solution next
152163 values = [x .value () for x in scope ]
153164
154-
155165 p_soft_con = (kappa_delta_p > 0 )
156166
157- # run with the objective
167+ # So a solution was found, try to find a better one now
168+ # set the objective
158169 s .maximize (p_soft_con )
159170
160- # So a solution was found, try to find a better one now
171+ # Give hint with previous solution to the solver
161172 s .solution_hint (scope , values )
162173
174+ # Solve with objective
163175 flag = s .solve (time_limit = self .time_limit , num_workers = 8 )
164176 if not flag :
165177 restore_scope_values (scope , values )
0 commit comments