1 |
On Fri, 09 Jun 2017 11:19:20 +0200 |
2 |
Michał Górny <mgorny@g.o> wrote: |
3 |
|
4 |
> On śro, 2017-06-07 at 11:56 +0200, Alexis Ballier wrote: |
5 |
> > On Wed, 07 Jun 2017 11:27:59 +0200 |
6 |
> > Michał Górny <mgorny@g.o> wrote: |
7 |
> > |
8 |
> > > On śro, 2017-06-07 at 10:17 +0200, Alexis Ballier wrote: |
9 |
> > > > > Also, do I presume correctly that for all supported cases |
10 |
> > > > > (i.e. those which your nsolve does not reject), solve and |
11 |
> > > > > nsolve are compatible? |
12 |
> > > > |
13 |
> > > > Not sure what you mean here. nsolve does not solve anything, it |
14 |
> > > > just validates REQUIRED_USE so that it is guaranteed it can be |
15 |
> > > > solved. |
16 |
> > > |
17 |
> > > What I mean is whether you can guarantee that: |
18 |
> > > |
19 |
> > > a. for every X that nsolve(X) == ok, solve() will be able to find |
20 |
> > > a valid solution, |
21 |
> > |
22 |
> > yes |
23 |
> > |
24 |
> > > b. for every X that solve() can solve reliably, nsolve(X) == ok. |
25 |
> > |
26 |
> > no and that's not really possible |
27 |
> |
28 |
> I thought so. To expand it a little, could you confirm whether I |
29 |
> correctly presume that: |
30 |
> |
31 |
> a. for all 'good' results, the plain iterative solve() should be able |
32 |
> to find a solution with a single iteration? |
33 |
|
34 |
yes that's the point of it |
35 |
|
36 |
> b. for all 'need toposort' results, the solve() should be able to find |
37 |
> a solution with n>=1 iterations? |
38 |
|
39 |
yes; though n is only bounded by the # of clauses (expanded as |
40 |
implications) while it can be 1 if reorderer properly; I wouldn't bother |
41 |
doing several iterations and just reject that at repoman side since it's |
42 |
easily solved |
43 |
|
44 |
> c. all of 'circular' results have at least one USE flag combination |
45 |
> that can not be solved by solve()? |
46 |
|
47 |
In theory no as that would imply your 1st b. In practice, I've only |
48 |
seen cases like that. |
49 |
|
50 |
|
51 |
|
52 |
> > > > We first need to define properly & formally how to solve requse |
53 |
> > > > constraints if we want ppl to be able to rely on it (or rather |
54 |
> > > > write requse that give the expected result). |
55 |
> > > > |
56 |
> > > > The way I see it, REQUIRED_USE ast looks like: |
57 |
> > > > (assuming ^^ is already expanded to || + ??) |
58 |
> > > > |
59 |
> > > > clause = |
60 |
> > > > AllOf(list of clauses) |
61 |
> > > > | AnyOf(list of clauses) |
62 |
> > > > | AtMostOne(list of clauses) |
63 |
> > > > | Implication(useflag, clause) |
64 |
> > > > | useflag |
65 |
> > > > |
66 |
> > > > Now, portage already has the function 'eval(input, clause)'. We |
67 |
> > > > need to define 'trueify(input, clause)' that modifies input so |
68 |
> > > > that 'eval(input, clause)' is always true afterwards. Since |
69 |
> > > > this is SAT, there is no hope to make this work for all |
70 |
> > > > clauses. From the discussions here, a good algorithm would be: |
71 |
> > > > |
72 |
> > > > trueify(input, clause) = match clause with |
73 |
> > > > AllOf(l) -> for i in l: trueify(input, i) |
74 |
> > > > > AnyOf(l) -> if not eval(input, clause): trueify(input, |
75 |
> > > > > l[0]) AtMostOne(l) -> f = (lambda x,y: pass) |
76 |
> > > > |
77 |
> > > > for i in l: |
78 |
> > > > f(input, i) |
79 |
> > > > if eval(input, i): f = falsify |
80 |
> > > > > Implication(useflag, consequence) -> |
81 |
> > > > |
82 |
> > > > if input[useflag]: trueify(input, |
83 |
> > > > consequence) |
84 |
> > > > > useflag -> input[useflag] = True |
85 |
> > > > |
86 |
> > > > |
87 |
> > > > Now you see that for the AtMostOne case we need its dual, the |
88 |
> > > > 'falsify(input, clause)' function: |
89 |
> > > > |
90 |
> > > > falsify(input, clause) = match clause with |
91 |
> > > > AllOf(l) -> falsify(input, l[0]) |
92 |
> > > |
93 |
> > > That's a debatable case. My solve() actually 'falsifies' all |
94 |
> > > the subexpressions which might be more reliable. |
95 |
> > |
96 |
> > Best way to debate this is probably to write the implication |
97 |
> > translation and feed that to nsolve from a few test cases. |
98 |
> |
99 |
> Exactly my thought. Since both algorithms should be kept in sync, it |
100 |
> probably makes sense to figure out the translation first and use |
101 |
> whatever makes it consistent without hacking on the translation hard. |
102 |
> I'll try to figure it out on paper today. |
103 |
> |
104 |
> > Intuition is that falsifying all of them adds more pressure on the |
105 |
> > solver and you might end up failing to solve it for no good reason, |
106 |
> > so falsifying only one of them seems safer. |
107 |
> |
108 |
> In either case, it's purely a matter of contract. You can't predict |
109 |
> which one will be more correct, and I think it's hard to even predict |
110 |
> which one developers will consider less surprising. |
111 |
|
112 |
Not sure what you mean by "more correct" but we've seen some |
113 |
translations are easier to solve than others, while being logically |
114 |
equivalent. |
115 |
|
116 |
As for what developers would expect, I think that'd be some kind of |
117 |
continuity of the solver: for 2 "close" inputs the solver gives "close" |
118 |
outputs (whatever close means). One way to go towards that is the least |
119 |
effort / least change from the solver part, which is why I always did |
120 |
the bare minimum of changes to the input in the algorithm I proposed. |
121 |
|
122 |
For example, if you negate them all in an AllOf clause, you get cases |
123 |
where a lot can change: |
124 |
For '?? ( a ( b c d e f ... z ) )' and USE="a b c ... z", it gives you |
125 |
USE="a -b -c -d ... -z" changing 25 useflags. |
126 |
If you just negate the first of the AllOf, you get as output |
127 |
USE="a -b c d ... z" changing only one useflag. |
128 |
|
129 |
[...] |
130 |
> > > > > Implication(useflag, consequence) -> |
131 |
> > > > |
132 |
> > > > if not input[useflag]: raise "impossible" |
133 |
> > > |
134 |
> > > Why impossible? Unless I'm missing something, it's false |
135 |
> > > already. |
136 |
> > |
137 |
> > 'foo? bar' is always true if foo is false; so it's impossible to |
138 |
> > make it false |
139 |
> |
140 |
> Yes, you are correct. I was actually thinking of 'if LHS is false, we |
141 |
> do not enforce RHS'. |
142 |
|
143 |
I'm wrong actually. It can be falsified by setting foo to True and bar |
144 |
to False. More on it below. |
145 |
|
146 |
> > it's really a corner case as I think we don't allow nested |
147 |
> > implications inside ||, ^^, () or ??, which is the only way to |
148 |
> > reach that. |
149 |
> |
150 |
> Strictly speaking, there's no rule prohibiting that. And I think we |
151 |
> actually have or had used 'foo?' inside '||' at least in dependencies. |
152 |
> The basic idea is that if the flag is disabled, the contents disappear |
153 |
> from the containing '||' block. |
154 |
|
155 |
Interesting. Then we should, sadly, support that. |
156 |
|
157 |
Let's think a bit about its meaning. |
158 |
?? ( X Y ) is "if X then not Y". |
159 |
?? ( a b? ( c ) ) is "if a then not "b? ( c )", that is, "if a then b |
160 |
and not c", so that's rewritten as "a? ( b !c )". |
161 |
|
162 |
That doesn't really seem to match your "basic idea". Instead, this |
163 |
could be rewritten as: |
164 |
b? ( ?? ( a c ) ) !b? ( ?? ( a ) ) |
165 |
that is: "b? ( a? ( !c ) )" |
166 |
|
167 |
"If a and b are enabled then disable c" seems a much better |
168 |
interpretation than "If a is enabled then enable b and disable c". |
169 |
|
170 |
|
171 |
Now, we can apply your basic idea to bubble up all the implications so |
172 |
that they're only at toplevel. |
173 |
|
174 |
Something([begin Implication(X,Y) end]) is rewritten as: |
175 |
Implication(X, Something([begin Y end])) |
176 |
Implication(!X, Something([begin end])) |
177 |
|
178 |
> Now, this makes a lot of things harder. For plain solve(), it's not |
179 |
> a major problem -- worst case, we put an extra reordering on each |
180 |
> iteration based on enabled USE flags. For nsolve(), it's harder since |
181 |
> it makes the iteration graph PITA. I'll have to think if we can even |
182 |
> translate it reasonably. |
183 |
|
184 |
|
185 |
With the above translation they should not make any difference for |
186 |
nsolve: the two implications have different domain as one is X -> |
187 |
something and the other is !X -> something. |
188 |
|
189 |
|
190 |
> > > > |
191 |
> > > > Note how the above favors leftmost in all cases. If it needs to |
192 |
> > > > change something, it always tries to leave the leftmost |
193 |
> > > > untouched. Note also that it processes everything left to right |
194 |
> > > > (the AllOf case where REQUIRED_USE="AllOf(list of clauses)" |
195 |
> > > > ). |
196 |
> > > |
197 |
> > > You need to be able to reorder the clauses to handle use.force |
198 |
> > > and use.mask. |
199 |
> > |
200 |
> > Not sure if reorder is the best way. It sure works, but maybe we'd |
201 |
> > want a repoman error if e.g. 'foo? ( bar )' is in REQUIRED_USE, bar |
202 |
> > is masked but not foo. That'd be a matter of eliminating the |
203 |
> > constants in the ast and if we get 'false' for a profile we error |
204 |
> > out. |
205 |
> |
206 |
> Yes, we want that. It makes sense for pure implications. For n-ary |
207 |
> thingies, it's harder than that and I'd rather not require developers |
208 |
> to figure out a specific order to make things work. |
209 |
> |
210 |
> Think of the following: |
211 |
> |
212 |
> || ( X Y ) |
213 |
> |
214 |
> with X being masked on profile A, Y being masked on profile B. You |
215 |
> just can't get it right then. |
216 |
> |
217 |
> Of course, this is a bit unrealistic but I think a case when a |
218 |
> preferred (newer) provider is masked on some architectures is |
219 |
> realistic. I don't think we want to prefer a worse solution (or go |
220 |
> back to applying package.use workarounds) because of fringe arches. |
221 |
> |
222 |
> Another question is whether we could solve this specific problem via |
223 |
> applying some kind of 'if forced/masked' logic into the AST. |
224 |
|
225 |
I think we're having communication interferences here :) That's exactly |
226 |
what I'm talking about. |
227 |
|
228 |
More specifically: |
229 |
|
230 |
For each profile: |
231 |
formula=replace_masks_and_force_by_constants(required_use) |
232 |
simplify(formula) |
233 |
nsolve(formula) |
234 |
|
235 |
In your example above, we'd call 'nsolve("|| ( X )")' and 'nsolve("|| |
236 |
( Y )")' (or even simpler, depending on how simplify() is defined). If |
237 |
both X and Y are masked on a profile, then that'd reduce to |
238 |
'nsolve("False")' which would rant. |
239 |
|
240 |
I think that for solve(), reordering is basically equivalent since we |
241 |
always favour leftmost. However, we have to somehow guarantee that |
242 |
solve() will never try to enable a masked flag. This is guaranteed by |
243 |
the above checks on profiles if enforced by repoman. If we apply the |
244 |
same logic for solve that'd basically be built-in (masked/forced flags |
245 |
wont appear in solve()'s input anymore), which I believe is better. Keep |
246 |
in mind that users can mask and force some useflags, so the repoman |
247 |
check won't catch that. |
248 |
|
249 |
|
250 |
> > > > So, the very first thing to do is to agree that the above solver |
251 |
> > > > (the trueify function) is what we want to implement and set |
252 |
> > > > this in stone. There's no point in implementing a proper requse |
253 |
> > > > checker if the algorithm is meant to change. Having a formal |
254 |
> > > > definition will also be necessary to mandate that in future |
255 |
> > > > EAPIs. |
256 |
> > > > |
257 |
> > > > Then, and only then, we'd need to have the above solver |
258 |
> > > > implemented into portage (hidden under a FEATURES) and import |
259 |
> > > > my nsolve into repoman (after due cleanup). |
260 |
> > > > |
261 |
> > > |
262 |
> > > Yes, that's my goal. However, before we can set the algorithm in |
263 |
> > > stone we need to verify that it will work in all of the supported |
264 |
> > > cases. |
265 |
> > |
266 |
> > Yep, that's the point of nsolve/classify :) |
267 |
> > |
268 |
> > > Preferably it should also be as simple as possible to avoid |
269 |
> > > putting too much complexity in the spec. |
270 |
> > |
271 |
> > Yes; not sure if anything simpler than the above can be achieved |
272 |
> > though. |
273 |
> |
274 |
> Actually, I think we should roughly proceed in the following way: |
275 |
> |
276 |
> 1. Design the solve() algorithm. |
277 |
> |
278 |
> 1a. Test solve() on isolated test cases. |
279 |
> |
280 |
> 1b. Integrate solve() into Portage (as optional feature) and encourage |
281 |
> wider testing. |
282 |
> |
283 |
> 2. Design the nsolve() verification based on solve(). |
284 |
> |
285 |
> 2a. Test nsolve() separately. |
286 |
> |
287 |
> 2b. Start fixing simple cases that do not bring any controversy (like |
288 |
> most of the reorderings). |
289 |
> |
290 |
> 2c. Integrate nsolve() into repoman. |
291 |
> |
292 |
> 3. Spec the whole thing and decide how to go next. |
293 |
> |
294 |
> |
295 |
> Assuming that 1* and 2* is to be done simultaneously, and each |
296 |
> subpoint implies that if we hit any walls, we can go back to a |
297 |
> previous point and fix the design. |
298 |
> |
299 |
> I think we're mostly past 1a and 2a now. We should try to think a bit |
300 |
> more about the corner cases right now, and when we're done with that, |
301 |
> the next step would be to: |
302 |
> |
303 |
> A. start fixing some cases ('need topo sort' at least?) to improve |
304 |
> testing area for the implementation, |
305 |
> |
306 |
> B. integrate solve() into Portage, |
307 |
> |
308 |
> C. maybe optionally integrate nsolve() into repoman. |
309 |
> |
310 |
> However, I don't want to set the spec into stone until we have some |
311 |
> live testing via Portage with FEATURES=solve-required-use or alike. |
312 |
> For repoman, we can add the verification optionally (i.e. with the |
313 |
> above feature or via a separate command-line option) but I don't |
314 |
> think we should enable it by default until we verify it. |
315 |
> |
316 |
|
317 |
Yes, I agree. Since solve() and nsolve() are very related we should |
318 |
carefully think how to organize the code then in order to avoid |
319 |
duplication or them getting out of sync. |
320 |
|
321 |
As for enabling it into repoman, I'm not sure which is best. Safest way |
322 |
is obviously as you suggest, but a warning is not a blocker, so |
323 |
enabling it by default can have a nice effect of receiving hate mails |
324 |
from angry developers if something is wrong. |
325 |
|
326 |
Alexis. |