cassowary-ruby 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,704 @@
1
+ # Copyright (C) 2012 by Tim Felgentreff
2
+
3
+ require "set"
4
+
5
+ module Cassowary
6
+ class SimplexSolver
7
+
8
+ attr_accessor :rows, :columns, :objective, :infeasible_rows,
9
+ :stay_plus_error_vars, :stay_minus_error_vars, :edit_vars,
10
+ :edit_constraints, :edit_plus_error_vars, :edit_minus_error_vars,
11
+ :prev_edit_constants, :new_edit_constants, :marker_vars,
12
+ :error_vars, :auto_solve
13
+
14
+ Epsilon = 1.0e-8
15
+
16
+ def add_bounds(var, lower = nil, upper = nil)
17
+ add_constraint lower.cn_leq(var) if lower
18
+ add_constraint var.cn_leq(upper) if upper
19
+ end
20
+
21
+ def add_constraint(constraint)
22
+ expr = make_expression(constraint)
23
+ unless try_adding_directly(expr)
24
+ add_with_artificial_variable(expr)
25
+ end
26
+ if auto_solve
27
+ optimize(objective)
28
+ set_external_variables
29
+ end
30
+ end
31
+
32
+ def remove_constraint(cn)
33
+ reset_stay_constants
34
+
35
+ # remove any error variables from the objective function
36
+ evars = error_vars.delete(cn) || []
37
+ zrow = objective
38
+ obj = rows[zrow]
39
+ evars.each do |v|
40
+ expr = rows[v]
41
+ if expr.nil?
42
+ obj.add_variable(v, cn.strength.symbolic_weight * -cn.weight, zrow, self)
43
+ else
44
+ obj.add_expression(expr, cn.strength.symbolic_weight * -cn.weight, zrow, self)
45
+ end
46
+ end
47
+
48
+ exit_var = nil
49
+ col = nil
50
+ min_ratio = 0
51
+
52
+ # try to make the marker variable basic, if it isn't already
53
+ marker = marker_vars.delete(cn)
54
+ unless rows.has_key? marker
55
+ # choose which variable to move out of the basis. only consider restricted basic vars
56
+ col = columns[marker]
57
+ col.each do |v|
58
+ if v.restricted?
59
+ expr = rows[v]
60
+ coeff = expr.coefficient_for(marker)
61
+ # only consider negative coefficients
62
+ if coeff < 0.0
63
+ r = 0.0 - expr.constant / coeff
64
+ if exit_var.nil? or r < min_ratio
65
+ min_ratio = r
66
+ exit_var = v
67
+ end
68
+ end
69
+ end
70
+ end
71
+
72
+ # If exitVar is still nil at this point, then either the
73
+ # marker variable has a positive coefficient in all equations,
74
+ # or it only occurs in equations for unrestricted variables.
75
+ # If it does occur in an equation for a restricted variable,
76
+ # pick the equation that gives the smallest ratio. (The row
77
+ # with the marker variable will become infeasible, but all the
78
+ # other rows will still be feasible; and we will be dropping
79
+ # the row with the marker variable. In effect we are removing
80
+ # the non-negativity restriction on the marker variable.)
81
+ if exit_var.nil?
82
+ col.each do |v|
83
+ if v.restricted?
84
+ expr = rows[v]
85
+ coeff = expr.coefficient_for(marker)
86
+ r = expr.constant / coeff
87
+ if exit_var.nil? or r < min_ratio
88
+ min_ratio = r
89
+ exit_var = v
90
+ end
91
+ end
92
+ end
93
+ end
94
+
95
+ # If exitVar is still nil, and col is empty, then exitVar
96
+ # doesn't occur in any equations, so just remove it.
97
+ # Otherwise pick an exit var from among the unrestricted
98
+ # variables whose equation involves the marker var
99
+ if exit_var.nil?
100
+ if col.empty?
101
+ remove_parametric_var(marker)
102
+ else
103
+ exit_var = col.to_a.first
104
+ end
105
+ end
106
+
107
+ if exit_var
108
+ pivot(marker, exit_var)
109
+ end
110
+ end
111
+
112
+ # Now delete any error variables. If cn is an inequality, it
113
+ # also contains a slack variable; but we use that as the
114
+ # marker variable and so it has been deleted when we removed
115
+ # its row
116
+ if rows.has_key?(marker)
117
+ remove_row(marker)
118
+ end
119
+ evars.each do |v|
120
+ remove_parametric_var(v) unless v == marker
121
+ end
122
+
123
+ if cn.stay_constraint?
124
+ self.stay_plus_error_vars = stay_plus_error_vars.reject do |v| evars.include? v end
125
+ self.stay_minus_error_vars = stay_minus_error_vars.reject do |v| evars.include? v end
126
+ end
127
+
128
+ if cn.edit_constraint?
129
+ # find the index in editPlusErrorVars of the error variable for this constraint
130
+ index = find_edit_error_index(evars)
131
+
132
+ # remove the error variables from editPlusErrorVars and editMinusErrorVars
133
+ edit_plus_error_vars.delete_at(index)
134
+ edit_minus_error_vars.delete_at(index)
135
+
136
+ # remove the constants from prevEditConstants
137
+ prev_edit_constants.delete_at(index)
138
+ end
139
+
140
+ if auto_solve
141
+ optimize(zrow)
142
+ set_external_variables
143
+ end
144
+ end
145
+
146
+ def resolve(cs = nil)
147
+ if cs
148
+ self.new_edit_constants = cs
149
+ end
150
+
151
+ # Re-solve the current collection of constraints for the new values in newEditConstants.
152
+ self.infeasible_rows = []
153
+ reset_stay_constants
154
+ reset_edit_constants
155
+ dual_optimize
156
+ set_external_variables
157
+ end
158
+
159
+ def solve
160
+ optimize objective
161
+ set_external_variables
162
+ end
163
+
164
+ def suggest_value(var, val)
165
+ edit_vars.each_with_index do |v, idx|
166
+ if v == var
167
+ new_edit_constants[idx] = val
168
+ end
169
+ return self
170
+ end
171
+ raise InternalError, "variable not currently being edited"
172
+ end
173
+
174
+ def add_edit_var(variable, strength)
175
+ add_constraint(EditConstraint.new variable: variable, strength: strength)
176
+ end
177
+
178
+ def add_stay(variable, strength = Strength::WeakStrength)
179
+ add_constraint(StayConstraint.new variable: variable, strength: strength)
180
+ end
181
+
182
+ def begin_edit
183
+ self.new_edit_constants = [nil] * edit_vars.size
184
+ end
185
+
186
+ def end_edit
187
+ edit_constraints.each do |cn|
188
+ remove_constraint(cn)
189
+ end
190
+ self.edit_vars = []
191
+ self.edit_constraints = []
192
+ end
193
+
194
+ def note_added_variable(var, subject)
195
+ if subject
196
+ columns[var] ||= Set.new
197
+ columns[var] << subject
198
+ end
199
+ end
200
+
201
+ def note_removed_variable(var, subject)
202
+ if subject
203
+ columns[var].delete(subject)
204
+ end
205
+ end
206
+
207
+ private
208
+ def add_row(var, expr)
209
+ rows[var] = expr
210
+ expr.each_variable_and_coefficient do |v, c|
211
+ columns[v] ||= Set.new
212
+ columns[v] << var
213
+ end
214
+ end
215
+
216
+ def add_with_artificial_variable(expr)
217
+ av = SlackVariable.new
218
+ az = ObjectiveVariable.new
219
+ azrow = LinearExpression.new
220
+
221
+ # the artificial objective is av, which we know is equal to expr
222
+ # (which contains only parametric variables)
223
+ azrow.constant = expr.constant
224
+ expr.each_variable_and_coefficient do |v, c|
225
+ azrow.terms[v] = c
226
+ end
227
+
228
+ add_row(az, azrow)
229
+ add_row(av, expr)
230
+
231
+ # try to optimize av to 0
232
+ optimize az
233
+
234
+ # Check that we were able to make the objective value 0. If
235
+ # not, the original constraint was unsatisfiable.
236
+ raise RequiredFailure unless azrow.constant.cl_approx_zero
237
+
238
+ if e = rows[av]
239
+ # Find another variable in this row and pivot, so that av
240
+ # becomes parametric. If there isn't another variable in the
241
+ # row then the tableau contains the equation av=0 -- just
242
+ # delete av's row.
243
+ if e.constant?
244
+ remove_row(av)
245
+ return nil
246
+ else
247
+ pivot(e.any_variable, av)
248
+ end
249
+ end
250
+
251
+ # av should be parametric at this point
252
+ remove_parametric_var av
253
+
254
+ # remove the temporary objective function
255
+ remove_row az
256
+ end
257
+
258
+ def choose_subject(expr)
259
+ # We are trying to add the constraint expr=0 to the tableaux.
260
+ # Try to choose a subject (a variable to become basic) from
261
+ # among the current variables in expr. If expr contains any
262
+ # unrestricted variables, then we must choose an unrestricted
263
+ # variable as the subject. Also, if the subject is new to the
264
+ # solver we won't have to do any substitutions, so we prefer new
265
+ # variables to ones that are currently noted as parametric. If
266
+ # expr contains only restricted variables, if there is a
267
+ # restricted variable with a negative coefficient that is new to
268
+ # the solver we can make that the subject. Otherwise we can't
269
+ # find a subject, so return nil. (In this last case we have to
270
+ # add an artificial variable and use that variable as the
271
+ # subject -- this is done outside this method though.)
272
+ #
273
+ # Note: in checking for variables that are new to the solver, we
274
+ # ignore whether a variable occurs in the objective function, since
275
+ # new slack variables are added to the objective function by
276
+ # 'makeExpression:', which is called before this method.
277
+ found_unrestricted = false
278
+ found_new_restricted = false
279
+ subject = nil
280
+ coeff = nil
281
+
282
+ expr.each_variable_and_coefficient do |v, c|
283
+ if found_unrestricted
284
+ # We have already found an unrestricted variable. The only
285
+ # time we will want to use v instead of the current choice
286
+ # 'subject' is if v is unrestricted and new to the solver
287
+ # and 'subject' isn't new. If this is the case just pick v
288
+ # immediately and return.
289
+ unless v.restricted?
290
+ return v unless columns.has_key? v
291
+ end
292
+ else
293
+ if v.restricted?
294
+ # v is restricted. If we have already found a suitable
295
+ # restricted variable just stick with that. Otherwise, if
296
+ # v is new to the solver and has a negative coefficient
297
+ # pick it. Regarding being new to the solver -- if the
298
+ # variable occurs only in the objective function we regard
299
+ # it as being new to the solver, since error variables are
300
+ # added to the objective function when we make the
301
+ # expression. We also never pick a dummy variable here.
302
+ if !found_new_restricted and !v.dummy? and c < 0.0
303
+ col = columns[v]
304
+ if col.nil? or (col.size == 1 and col.include? objective)
305
+ subject = v
306
+ found_new_restricted = true
307
+ end
308
+ end
309
+ else
310
+ # v is unrestricted. If v is also new to the solver just
311
+ # pick it now
312
+ return v unless columns.has_key? v
313
+ subject = v
314
+ found_unrestricted = true
315
+ end
316
+ end
317
+ end
318
+
319
+ # subject is nil. Make one last check -- if all of the
320
+ # variables in expr are dummy variables, then we can pick a
321
+ # dummy variable as the subject.
322
+ return subject unless subject.nil?
323
+ expr.each_variable_and_coefficient do |v, c|
324
+ return nil unless v.dummy?
325
+ # if v is new to the solver tentatively make it the subject
326
+ unless columns.has_key? v
327
+ subject = v
328
+ coeff = c
329
+ end
330
+ end
331
+
332
+ # If we get this far, all of the variables in the expression
333
+ # should be dummy variables. If the constant is nonzero we are
334
+ # trying to add an unsatisfiable required constraint. (Remember
335
+ # that dummy variables must take on a value of 0.) Otherwise,
336
+ # if the constant is zero, multiply by -1 if necessary to make
337
+ # the coefficient for the subject negative.
338
+ raise RequiredFailure unless expr.constant.cl_approx_zero
339
+ if coeff > 0
340
+ expr.each_variable_and_coefficient do |v, c|
341
+ expr.terms[v] = 0.0 - c
342
+ end
343
+ end
344
+
345
+ subject
346
+ end
347
+
348
+ def delta_edit_constant(delta, plus_error_var, minus_error_var)
349
+ if expr = rows[plus_error_var]
350
+ expr.increment_constant delta
351
+ # error variables are always restricted -- so the row is
352
+ # infeasible if the constant is negative
353
+ (infeasible_rows << plus_error_var) if expr.constant < 0.0
354
+ return nil
355
+ end
356
+
357
+ if expr = rows[minus_error_var]
358
+ expr.increment_constant -delta
359
+ (infeasible_rows << plus_error_var) if expr.constant < 0.0
360
+ return nil
361
+ end
362
+
363
+ # Neither minusErrorVar nor plusErrorVar is basic. So they must
364
+ # both be nonbasic, and will both occur in exactly the same
365
+ # expressions. Find all the expressions in which they occur by
366
+ # finding the column for the minusErrorVar (it doesn't matter
367
+ # whether we look for that one or for plusErrorVar). Fix the
368
+ # constants in these expressions.
369
+ columns[minus_error_var].each do |basic_var|
370
+ expr = rows[basic_var]
371
+ c = expr.coefficient_for(minus_error_var)
372
+ expr.increment_constant c * delta
373
+ if basic_var.restricted? and expr.constant < 0.0
374
+ infeasible_rows << basic_var
375
+ end
376
+ end
377
+ end
378
+
379
+ def dual_optimize
380
+ # We have set new values for the constants in the edit
381
+ # constraints. Re-optimize using the dual simplex algorithm.
382
+ entry_var = nil
383
+ zrow = rows[objective]
384
+ until infeasible_rows.empty?
385
+ exit_var = infeasible_rows.shift
386
+ if expr = rows[exit_var]
387
+ if expr.constant < 0.0
388
+ ratio = nil
389
+ expr.each_variable_and_coefficient do |v, c|
390
+ if c > 0.0 and v.pivotable?
391
+ zc = zrow.terms[v]
392
+ r = zc ? zc / c : SymbolicWeight::Zero
393
+ if ratio.nil? or r < ratio or (r == ratio and v.hash < entry_var.hash)
394
+ entry_var = v
395
+ ratio = r
396
+ end
397
+ end
398
+ end
399
+ raise InternalError if ratio.nil?
400
+ pivot entry_var, exit_var
401
+ end
402
+ end
403
+ end
404
+ end
405
+
406
+ def find_edit_error_index(evars)
407
+ evars.each do |v|
408
+ if index = edit_plus_error_vars.index(v)
409
+ return index
410
+ end
411
+ end
412
+ raise InternalError, "didn't find a variable"
413
+ end
414
+
415
+ def initialize
416
+ self.objective = ObjectiveVariable.new
417
+ self.rows = {objective => LinearExpression.new_with_symbolic_weight}
418
+ self.columns = {}
419
+ self.infeasible_rows = []
420
+ self.prev_edit_constants = []
421
+ self.stay_plus_error_vars = []
422
+ self.stay_minus_error_vars = []
423
+ self.edit_vars = []
424
+ self.edit_constraints = []
425
+ self.edit_plus_error_vars = []
426
+ self.edit_minus_error_vars = []
427
+ self.marker_vars = {}
428
+ self.error_vars = {}
429
+ self.auto_solve = true
430
+ end
431
+
432
+ def make_expression(cn)
433
+ # Make a new linear expression representing the constraint cn,
434
+ # replacing any basic variables with their defining expressions.
435
+ # Normalize if necessary so that the constant is non-negative.
436
+ # If the constraint is non-required give its error variables an
437
+ # appropriate weight in the objective function.
438
+ expr = LinearExpression.new
439
+ cnexpr = cn.expression
440
+ expr.constant = cnexpr.constant
441
+ cnexpr.each_variable_and_coefficient do |v, c|
442
+ e = rows[v]
443
+ if e.nil?
444
+ expr.add_variable(v, c)
445
+ else
446
+ expr.add_expression(e, c)
447
+ end
448
+ end
449
+
450
+ # add slack and error variables as needed
451
+ if cn.inequality?
452
+ # cn is an inequality, so add a slack variable. The original
453
+ # constraint is expr>=0, so that the resulting equality is
454
+ # expr-slackVar=0. If cn is also non-required add a negative
455
+ # error variable, giving expr-slackVar = -errorVar, in other
456
+ # words expr-slackVar+errorVar=0. Since both of these
457
+ # variables are newly created we can just add them to the
458
+ # expression (they can't be basic).
459
+ slackvar = SlackVariable.new
460
+ expr.terms[slackvar] = -1.0
461
+ marker_vars[cn] = slackvar
462
+ unless cn.required?
463
+ eminus = SlackVariable.new
464
+ expr.terms[eminus] = 1.0
465
+
466
+ zrow = rows[objective]
467
+ zrow.terms[eminus] = cn.strength.symbolic_weight * cn.weight
468
+ error_vars[cb] = [eminus]
469
+ note_added_variable(eminus, objective)
470
+ end
471
+ else
472
+ if cn.required?
473
+ # Add a dummy variable to the expression to serve as a
474
+ # marker for this constraint. The dummy variable is never
475
+ # allowed to enter the basis when pivoting.
476
+ dummyvar = DummyVariable.new
477
+ expr.terms[dummyvar] = 1.0
478
+ marker_vars[cn] = dummyvar
479
+ else
480
+ # cn is a non-required equality. Add a positive and a
481
+ # negative error variable, making the resulting constraint
482
+ # expr = eplus - eminus, in other words expr-eplus+eminus=0
483
+ eplus = SlackVariable.new
484
+ eminus = SlackVariable.new
485
+ expr.terms[eplus] = -1.0
486
+ expr.terms[eminus] = 1.0
487
+
488
+ # index the constraint under one of the error variables
489
+ marker_vars[cn] = eplus
490
+ zrow = rows[objective]
491
+ zrow.terms[eplus] = cn.strength.symbolic_weight * cn.weight
492
+ note_added_variable(eplus, objective)
493
+ zrow.terms[eminus] = cn.strength.symbolic_weight * cn.weight
494
+ error_vars[cn] = [eplus, eminus]
495
+ note_added_variable(eminus, objective)
496
+
497
+ if cn.stay_constraint?
498
+ stay_plus_error_vars << eplus
499
+ stay_minus_error_vars << eminus
500
+ end
501
+
502
+ if cn.edit_constraint?
503
+ edit_vars << cn.variable
504
+ edit_constraints << cn
505
+ edit_plus_error_vars << eplus
506
+ edit_minus_error_vars << eminus
507
+ prev_edit_constants << cnexpr.constant
508
+ end
509
+ end
510
+ end
511
+
512
+ # The constant in the expression should be non-negative. If
513
+ # necessary normalize the expression by multiplying by -1.
514
+ if expr.constant < 0
515
+ expr.constant = 0.0 - expr.constant
516
+ expr.each_variable_and_coefficient do |v, c|
517
+ expr.terms[v] = 0.0 - c
518
+ end
519
+ end
520
+ expr
521
+ end
522
+
523
+ def optimize(zvar)
524
+ # Minimize the value of the objective. (The tableau should
525
+ # already be feasible.)
526
+ zrow = rows[zvar]
527
+ exitvar = nil
528
+ while true do
529
+ # Find a variable in the objective function with a negative
530
+ # coefficient (ignoring dummy variables). If all coefficients
531
+ # are positive we're done. To implement Bland's anticycling
532
+ # rule, if there is more than one variable with a negative
533
+ # coefficient, pick the one with the smaller id (implemented
534
+ # as hash).
535
+ entryvar = nil
536
+ zrow.each_variable_and_coefficient do |v, c|
537
+ if v.pivotable? and c.definitely_negative and (entryvar.nil? or v.hash < entryvar.hash)
538
+ entryvar = v
539
+ end
540
+ end
541
+
542
+ # if all coefficients were positive (or if the objective
543
+ # function has no pivotable variables) we are at optimum
544
+ return nil if entryvar.nil?
545
+
546
+ # Choose which variable to move out of the basis. Only
547
+ # consider pivotable basic variables (that is, restricted,
548
+ # non-dummy variables).
549
+ minratio = nil
550
+ columns[entryvar].each do |v|
551
+ if v.pivotable?
552
+ expr = rows[v]
553
+ coeff = expr.coefficient_for(entryvar)
554
+
555
+ if coeff < 0.0
556
+ r = -(expr.constant / coeff)
557
+ # Decide whether to make v be the best choice for exit
558
+ # variable so far by comparing the ratios. In case of a
559
+ # tie, choose the variable with the smaller id (to
560
+ # implement Bland's anticycling rule).
561
+ if minratio.nil? or r < minratio or (r == minratio and v.hash < exitvar.hash)
562
+ minratio = r
563
+ exitvar = v
564
+ end
565
+ end
566
+ end
567
+ end
568
+
569
+ # If minRatio is still nil at this point, it means that the
570
+ # objective function is unbounded, i.e. it can become
571
+ # arbitrarily negative. This should never happen in this
572
+ # application.
573
+ raise InternalError if minratio.nil?
574
+ pivot entryvar, exitvar
575
+ end
576
+ end
577
+
578
+ def pivot(entryvar, exitvar)
579
+ # Do a pivot. Move entryVar into the basis (i.e. make it a
580
+ # basic variable), and move exitVar out of the basis (i.e. make
581
+ # it a parametric variable). expr is the expression for the
582
+ # exit variable (about to leave the basis) -- so that the old
583
+ # tableau includes the equation exitVar = expr
584
+ expr = remove_row(exitvar)
585
+
586
+ # Compute an expression for the entry variable. Since expr has
587
+ # been deleted from the tableau we can destructively modify it
588
+ # to build this expression.
589
+ expr.change_subject exitvar, entryvar
590
+ substitute_out(entryvar, expr)
591
+ add_row(entryvar, expr)
592
+ end
593
+
594
+ def remove_parametric_var(var)
595
+ set = columns.delete(var)
596
+ set.each do |v|
597
+ rows[v].terms.delete(var)
598
+ end
599
+ end
600
+
601
+ def remove_row(var)
602
+ expr = rows.delete(var)
603
+ expr.each_variable_and_coefficient do |v, c|
604
+ columns[v].delete var
605
+ end
606
+ infeasible_rows.delete(var)
607
+ expr
608
+ end
609
+
610
+ def reset_edit_constants
611
+ # Each of the non-required edits will be represented by an
612
+ # equation of the form
613
+ #
614
+ # v = c + eplus - eminus
615
+ #
616
+ # where v is the variable with the edit, c is the previous edit
617
+ # value, and eplus and eminus are slack variables that hold the
618
+ # error in satisfying the edit constraint. We are about to
619
+ # change something, and we want to fix the constants in the
620
+ # equations representing the edit constraints. If one of eplus
621
+ # and eminus is basic, the other must occur only in the
622
+ # expression for that basic error variable. (They can't both be
623
+ # basic.) Fix the constant in this expression. Otherwise they
624
+ # are both nonbasic. Find all of the expressions in which they
625
+ # occur, and fix the constants in those. See the UIST paper for
626
+ # details.
627
+
628
+ raise InternalError if new_edit_constants.size != edit_plus_error_vars.size
629
+ new_edit_constants.each_with_index do |ec, idx|
630
+ delta = ec - prev_edit_constants[idx]
631
+ prev_edit_constants[idx] = ec
632
+ delta_edit_constant(delta, edit_plus_error_vars[idx], edit_minus_error_vars[idx])
633
+ end
634
+ end
635
+
636
+ def reset_stay_constants
637
+ # Each of the non-required stays will be represented by an
638
+ # equation of the form
639
+ #
640
+ # v = c + eplus - eminus
641
+ #
642
+ # where v is the variable with the stay, c is the previous value
643
+ # of v, and eplus and eminus are slack variables that hold the
644
+ # error in satisfying the stay constraint. We are about to
645
+ # change something, and we want to fix the constants in the
646
+ # equations representing the stays. If both eplus and eminus
647
+ # are nonbasic they have value 0 in the current solution,
648
+ # meaning the previous stay was exactly satisfied. In this case
649
+ # nothing needs to be changed. Otherwise one of them is basic,
650
+ # and the other must occur only in the expression for that basic
651
+ # error variable. Reset the constant in this expression to 0.
652
+
653
+ stay_plus_error_vars.each_with_index do |ev, idx|
654
+ expr = rows[ev] || rows[stay_minus_error_vars[idx]]
655
+ expr.constant = 0.0 if expr
656
+ end
657
+ end
658
+
659
+ def set_external_variables
660
+ # Set each external basic variable to its value, and set each
661
+ # external parametric variable to 0. (It isn't clear that we
662
+ # will ever have external parametric variables -- every external
663
+ # variable should either have a stay on it, or have an equation
664
+ # that defines it in terms of other external variables that do
665
+ # have stays. For the moment I'll put this in though.)
666
+ # Variables that are internal to the solver don't actually store
667
+ # values -- their values are just implicit in the tableu -- so
668
+ # we don't need to set them.
669
+ rows.each_pair do |var, expr|
670
+ var.value = expr.constant if var.external?
671
+ end
672
+
673
+ columns.keys.each do |var|
674
+ var.value = 0.0 if var.external?
675
+ end
676
+ end
677
+
678
+ def substitute_out(old_var, expr)
679
+ col = columns.delete(old_var)
680
+ col.each do |v|
681
+ row = rows[v]
682
+ row.substitute_variable(old_var, expr, v, self)
683
+ if v.restricted? and row.constant < 0.0
684
+ infeasible_rows << v
685
+ end
686
+ end
687
+ end
688
+
689
+ def try_adding_directly(expr)
690
+ # If possible choose a subject for expr (a variable to become
691
+ # basic) from among the current variables in expr. If this
692
+ # isn't possible, add an artificial variable and use that
693
+ # variable as the subject.
694
+ subject = choose_subject(expr)
695
+ return false if subject.nil?
696
+ expr.new_subject subject
697
+ if columns.has_key? subject
698
+ substitute_out subject, expr
699
+ end
700
+ add_row subject, expr
701
+ true
702
+ end
703
+ end
704
+ end