001package org.cpsolver.ifs.criteria;
002
003import java.util.ArrayList;
004import java.util.Collection;
005import java.util.Locale;
006import java.util.Map;
007import java.util.Set;
008
009import org.cpsolver.coursett.criteria.TimetablingCriterion;
010import org.cpsolver.ifs.assignment.Assignment;
011import org.cpsolver.ifs.assignment.context.AssignmentConstraintContext;
012import org.cpsolver.ifs.assignment.context.AssignmentContext;
013import org.cpsolver.ifs.assignment.context.AssignmentContextHelper;
014import org.cpsolver.ifs.assignment.context.AssignmentContextReference;
015import org.cpsolver.ifs.assignment.context.CanHoldContext;
016import org.cpsolver.ifs.assignment.context.ConstraintWithContext;
017import org.cpsolver.ifs.assignment.context.HasAssignmentContext;
018import org.cpsolver.ifs.model.Constraint;
019import org.cpsolver.ifs.model.Model;
020import org.cpsolver.ifs.model.Value;
021import org.cpsolver.ifs.model.Variable;
022import org.cpsolver.ifs.solver.Solver;
023import org.cpsolver.ifs.util.DataProperties;
024
025
026/**
027 * Abstract Criterion. <br>
028 * <br>
029 * An optimization objective can be split into several (optimization) criteria
030 * and modeled as a weighted sum of these. This makes the implementation of a particular problem
031 * more versatile as it allows for an easier modification of the optimization objective.
032 * <br>
033 * This class implements most of the {@link Criterion} except of the {@link Criterion#getValue(Assignment, Value, Set)}.
034 * 
035 * @version IFS 1.3 (Iterative Forward Search)<br>
036 *          Copyright (C) 2006 - 2014 Tomáš Müller<br>
037 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
038 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
039 * <br>
040 *          This library is free software; you can redistribute it and/or modify
041 *          it under the terms of the GNU Lesser General Public License as
042 *          published by the Free Software Foundation; either version 3 of the
043 *          License, or (at your option) any later version. <br>
044 * <br>
045 *          This library is distributed in the hope that it will be useful, but
046 *          WITHOUT ANY WARRANTY; without even the implied warranty of
047 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
048 *          Lesser General Public License for more details. <br>
049 * <br>
050 *          You should have received a copy of the GNU Lesser General Public
051 *          License along with this library; if not see
052 *          <a href='http://www.gnu.org/licenses/'>http://www.gnu.org/licenses/</a>.
053 * @param <V> Variable
054 * @param <T> Value
055 */
056public abstract class AbstractCriterion<V extends Variable<V, T>, T extends Value<V, T>> implements Criterion<V, T>, HasAssignmentContext<V, T, AbstractCriterion<V,T>.ValueContext>, CanHoldContext {
057    private Model<V, T> iModel;
058    protected double iBest = 0.0, iWeight = 0.0;
059    protected static java.text.DecimalFormat sDoubleFormat = new java.text.DecimalFormat("0.##",
060            new java.text.DecimalFormatSymbols(Locale.US));
061    protected static java.text.DecimalFormat sPercentFormat = new java.text.DecimalFormat("0.##",
062            new java.text.DecimalFormatSymbols(Locale.US));
063    protected boolean iDebug = false;
064    
065    private AssignmentContextReference<V, T, ValueContext> iContextReference = null;
066    private AssignmentContext[] iContext = new AssignmentContext[CanHoldContext.sMaxSize];
067    private int iLastCacheId = 0;
068
069    
070    /**
071     * Defines how the overall value of the criterion should be automatically updated (using {@link Criterion#getValue(Value, Set)}).
072     */
073    protected static enum ValueUpdateType {
074        /** Update is done before an unassignment (decrement) and before an assignment (increment). */
075        BeforeUnassignedBeforeAssigned,
076        /** Update is done after an unassignment (decrement) and before an assignment (increment). */
077        AfterUnassignedBeforeAssigned,
078        /** Update is done before an unassignment (decrement) and after an assignment (increment). */
079        BeforeUnassignedAfterAssigned,
080        /** Update is done after an unassignment (decrement) and after an assignment (increment). This is the default. */
081        AfterUnassignedAfterAssigned,
082        /** Criterion is to be updated manually (e.g., using {@link Criterion#inc(Assignment, double)}). */
083        NoUpdate
084    }
085    private ValueUpdateType iValueUpdateType = ValueUpdateType.BeforeUnassignedBeforeAssigned;
086
087    /** Defines weight name (to be used to get the criterion weight from the configuration). 
088     * @return name of the weight associated with this criterion
089     **/
090    public String getWeightName() {
091        return "Weight." + getClass().getName().substring(1 + getClass().getName().lastIndexOf('.'));
092    }
093    
094    /** Defines default weight (when {@link AbstractCriterion#getWeightName()} parameter is not present in the criterion).
095     * @param config solver configuration
096     * @return default criterion weight value
097     **/
098    public double getWeightDefault(DataProperties config) {
099        return 0.0;
100    }
101    
102    @Override
103    public void setModel(Model<V,T> model) {
104        iModel = model;
105        if (model != null)
106            iContextReference = model.createReference(this);
107    }
108    
109    @Override
110    public void configure(DataProperties properties) {
111        iWeight = properties.getPropertyDouble(getWeightName(), getWeightDefault(properties));
112        iDebug = properties.getPropertyBoolean("Debug." + getClass().getName().substring(1 + getClass().getName().lastIndexOf('.')), properties.getPropertyBoolean("Debug.Criterion", false));
113    }
114
115    @Override
116    public boolean init(Solver<V, T> solver) {
117        configure(solver.getProperties());
118        return true;
119    }
120    
121    /**
122     * Returns current model
123     * @return problem model
124     **/
125    public Model<V, T> getModel() { return iModel; }
126    
127    /**
128     * Returns an assignment context associated with this criterion. If there is no 
129     * assignment context associated with this criterion yet, one is created using the
130     * {@link ConstraintWithContext#createAssignmentContext(Assignment)} method. From that time on,
131     * this context is kept with the assignment and automatically updated by calling the
132     * {@link AssignmentConstraintContext#assigned(Assignment, Value)} and {@link AssignmentConstraintContext#unassigned(Assignment, Value)}
133     * whenever a variable is changed as given by the {@link ValueUpdateType}.
134     * @param assignment given assignment
135     * @return assignment context associated with this constraint and the given assignment
136     */
137    @Override
138    public ValueContext getContext(Assignment<V, T> assignment) {
139        return AssignmentContextHelper.getContext(this, assignment);
140    }
141    
142    @Override
143    public ValueContext createAssignmentContext(Assignment<V,T> assignment) {
144        return new ValueContext(assignment);
145    }
146
147    @Override
148    public AssignmentContextReference<V, T, ValueContext> getAssignmentContextReference() { return iContextReference; }
149
150    @Override
151    public void setAssignmentContextReference(AssignmentContextReference<V, T, ValueContext> reference) { iContextReference = reference; }
152
153    @Override
154    public AssignmentContext[] getContext() {
155        return iContext;
156    }
157    
158    @Override
159    public double getValue(Assignment<V, T> assignment) {
160        return getContext(assignment).getTotal();
161    }
162    
163    @Override
164    public double getBest() {
165        return iBest;
166    }
167    
168    @Override
169    public double getValue(Assignment<V, T> assignment, Collection<V> variables) {
170        double ret = 0;
171        for (V v: variables) {
172            T t = assignment.getValue(v);
173            if (t != null) ret += getValue(assignment, t, null);
174        }
175        return ret;
176    }
177
178    
179    @Override
180    public double getWeight() {
181        return iWeight;
182    }
183    
184    @Override
185    public double getWeightedBest() {
186        return getWeight() == 0.0 ? 0.0 : getWeight() * getBest();
187    }
188    
189    @Override
190    public double getWeightedValue(Assignment<V, T> assignment) {
191        return (getWeight() == 0.0 ? 0.0 : getWeight() * getValue(assignment));
192    }
193    
194    @Override
195    public double getWeightedValue(Assignment<V, T> assignment, T value, Set<T> conflicts) {
196        return (getWeight() == 0.0 ? 0.0 : getWeight() * getValue(assignment, value, conflicts));
197    }
198    
199    @Override
200    public double getWeightedValue(Assignment<V, T> assignment, Collection<V> variables) {
201        return (getWeight() == 0.0 ? 0.0 : getWeight() * getValue(assignment, variables));
202    }
203
204    /** Compute bounds (bounds are being cached by default). 
205     * @param assignment current assignment
206     * @return minimum and maximum of this criterion's value
207     **/
208    protected double[] computeBounds(Assignment<V, T> assignment) {
209        return getBounds(assignment, new ArrayList<V>(getModel().variables()));
210    }
211
212    @Override
213    public double[] getBounds(Assignment<V, T> assignment) {
214        return getContext(assignment).getBounds(assignment);
215    }
216
217    @Override
218    public double[] getBounds(Assignment<V, T> assignment, Collection<V> variables) {
219        double[] bounds = new double[] { 0.0, 0.0 };
220        for (V v: variables) {
221            Double min = null, max = null;
222            for (T t: v.values(assignment)) {
223                double value = getValue(assignment, t, null);
224                if (min == null) { min = value; max = value; continue; }
225                min = Math.min(min, value);
226                max = Math.max(max, value);
227            }
228            if (min != null) {
229                bounds[0] += min;
230                bounds[1] += max;
231            }
232        }
233        return bounds;
234    }
235
236    @Override
237    public void beforeAssigned(Assignment<V, T> assignment, long iteration, T value) {
238        switch (getValueUpdateType()) {
239            case AfterUnassignedBeforeAssigned:
240            case BeforeUnassignedBeforeAssigned:
241                getContext(assignment).assigned(assignment, value);
242        }
243    }
244
245    @Override
246    public void afterAssigned(Assignment<V, T> assignment, long iteration, T value) {
247        switch (getValueUpdateType()) {
248            case AfterUnassignedAfterAssigned:
249            case BeforeUnassignedAfterAssigned:
250                getContext(assignment).assigned(assignment, value);
251        }
252    }
253
254    @Override
255    public void beforeUnassigned(Assignment<V, T> assignment, long iteration, T value) {
256        switch (getValueUpdateType()) {
257            case BeforeUnassignedAfterAssigned:
258            case BeforeUnassignedBeforeAssigned:
259                getContext(assignment).unassigned(assignment, value);
260        }
261    }
262
263    @Override
264    public void afterUnassigned(Assignment<V, T> assignment, long iteration, T value) {
265        switch (getValueUpdateType()) {
266            case AfterUnassignedAfterAssigned:
267            case AfterUnassignedBeforeAssigned:
268                getContext(assignment).unassigned(assignment, value);
269        }
270    }
271
272    @Override
273    public void bestSaved(Assignment<V, T> assignment) {
274        iBest = getContext(assignment).getTotal();
275    }
276
277    @Override
278    public void bestRestored(Assignment<V, T> assignment) {
279        getContext(assignment).setTotal(iBest);
280    }
281    
282    @Override
283    public void inc(Assignment<V, T> assignment, double value) {
284        getContext(assignment).inc(value);
285    }   
286
287    @Override
288    public String getName() {
289        return getClass().getName().substring(1 + getClass().getName().lastIndexOf('.')).replaceAll("(?<=[^A-Z])([A-Z])"," $1");
290    }
291    
292    /** Clear bounds cache */
293    protected void clearCache() {
294        iLastCacheId++;
295    }
296    
297    @Override
298    public void variableAdded(V variable) {
299        clearCache();
300    }
301    
302    @Override
303    public void variableRemoved(V variable) {
304        clearCache();
305    }
306    
307    @Override
308    public void constraintAdded(Constraint<V, T> constraint) {
309        clearCache();
310    }
311    
312    @Override
313    public void constraintRemoved(Constraint<V, T> constraint) {
314        clearCache();
315    }
316    
317    protected String getPerc(double value, double min, double max) {
318        if (max == min)
319            return sPercentFormat.format(100.0);
320        return sPercentFormat.format(100.0 - 100.0 * (value - min) / (max - min));
321    }
322
323    protected String getPercRev(double value, double min, double max) {
324        if (max == min)
325            return sPercentFormat.format(0.0);
326        return sPercentFormat.format(100.0 * (value - min) / (max - min));
327    }
328
329    @Override
330    public void getInfo(Assignment<V, T> assignment, Map<String, String> info) {
331    }
332    
333    @Override
334    public void getInfo(Assignment<V, T> assignment, Map<String, String> info, Collection<V> variables) {
335    }
336    
337    /**
338     * Return formatted value of this criterion, as percentage when possible 
339     * @param assignment current assignment
340     * @return formatted value
341     */
342    public String getPercentage(Assignment<V, T> assignment) {
343        double val = getValue(assignment);
344        double[] bounds = getBounds(assignment);
345        if (bounds[0] <= val && val <= bounds[1] && bounds[0] < bounds[1])
346            return getPerc(val, bounds[0], bounds[1]) + "%";
347        else if (bounds[1] <= val && val <= bounds[0] && bounds[1] < bounds[0])
348            return getPercRev(val, bounds[1], bounds[0]) + "%";
349        else
350            return sDoubleFormat.format(val);
351    }
352    
353    @Override
354    public void getExtendedInfo(Assignment<V, T> assignment, Map<String, String> info) {
355        if (iDebug) {
356            double val = getValue(assignment), w = getWeightedValue(assignment), prec = getValue(assignment, getModel().variables());
357            double[] bounds = getBounds(assignment);
358            if (bounds[0] <= val && val <= bounds[1] && bounds[0] < bounds[1])
359                info.put("[C] " + getName(),
360                        getPerc(val, bounds[0], bounds[1]) + "% (value: " + sDoubleFormat.format(val) +
361                        (Math.abs(prec - val) > 0.0001 ? ", precise:" + sDoubleFormat.format(prec) : "") +
362                        ", weighted:" + sDoubleFormat.format(w) +
363                        ", bounds: " + sDoubleFormat.format(bounds[0]) + ".." + sDoubleFormat.format(bounds[1]) + ")");
364            else if (bounds[1] <= val && val <= bounds[0] && bounds[1] < bounds[0])
365                info.put("[C] " + getName(),
366                        getPercRev(val, bounds[1], bounds[0]) + "% (value: " + sDoubleFormat.format(val) +
367                        (Math.abs(prec - val) > 0.0001 ? ", precise:" + sDoubleFormat.format(prec) : "") +
368                        ", weighted:" + sDoubleFormat.format(w) +
369                        ", bounds: " + sDoubleFormat.format(bounds[1]) + ".." + sDoubleFormat.format(bounds[0]) + ")");
370            else if (bounds[0] != val || val != bounds[1])
371                info.put("[C] " + getName(),
372                        sDoubleFormat.format(val) + " (" +
373                        (Math.abs(prec - val) > 0.0001 ? "precise:" + sDoubleFormat.format(prec) + ", ": "") +
374                        "weighted:" + sDoubleFormat.format(w) +
375                        (bounds[0] != bounds[1] ? ", bounds: " + sDoubleFormat.format(bounds[0]) + ".." + sDoubleFormat.format(bounds[1]) : "") +
376                        ")");
377        }        
378    }
379    
380    /**
381     * Assignment context holding current value and the cached bounds.
382     */
383    public class ValueContext implements AssignmentContext {
384        protected double iTotal = 0.0;
385        private double[] iBounds = null;
386        private int iCacheId = -1;
387
388        /** Create from an assignment 
389         * @param assignment current assignment
390         **/
391        protected ValueContext(Assignment<V, T> assignment) {
392            if (getValueUpdateType() != ValueUpdateType.NoUpdate)
393                iTotal = AbstractCriterion.this.getValue(assignment, getModel().variables());
394        }
395        
396        protected ValueContext() {}
397        
398        /** Update value when unassigned
399         * @param assignment current assignment
400         * @param value recently unassigned value
401         **/
402        protected void unassigned(Assignment<V, T> assignment, T value) {
403            iTotal -= getValue(assignment, value, null);
404        }
405        
406        /** Update value when assigned 
407         * @param assignment current assignment
408         * @param value recently assigned value
409         **/
410        protected void assigned(Assignment<V, T> assignment, T value) {
411            iTotal += getValue(assignment, value, null);
412        }
413
414        /** Return value 
415         * @return current value of the criterion
416         **/
417        public double getTotal() { return iTotal; }
418        
419        /** Set value
420         * @param value current value of the criterion
421         **/
422        public void setTotal(double value) { iTotal = value; }
423        
424        /** Increment value
425         * @param value increment
426         **/
427        public void inc(double value) { iTotal += value; }
428        
429        /** Return bounds 
430         * @param assignment current assignment 
431         * @return minimum and maximum of this criterion's value
432         **/
433        protected double[] getBounds(Assignment<V, T> assignment) {
434            if (iBounds == null || iCacheId < iLastCacheId) {
435                iCacheId = iLastCacheId;
436                iBounds = computeBounds(assignment);
437            }
438            return (iBounds == null ? new double[] {0.0, 0.0} : iBounds);
439        }
440        
441        /** Set bounds 
442         * @param bounds bounds to cache
443         **/
444        protected void setBounds(double[] bounds) {
445            iBounds = bounds;
446        }
447    }
448
449    @Override
450    @Deprecated
451    public double getWeightedValue() {
452        return getWeightedValue(getModel().getDefaultAssignment());
453    }
454
455    @Override
456    @Deprecated
457    public double[] getBounds() {
458        return getBounds(getModel().getDefaultAssignment());
459    }
460
461    @Override
462    @Deprecated
463    public double getWeightedValue(T value, Set<T> conflicts) {
464        return getWeightedValue(getModel().getDefaultAssignment(), value, conflicts);
465    }
466    
467    @Override
468    @Deprecated
469    public double getValue(T value, Set<T> conflicts) {
470        return getValue(getModel().getDefaultAssignment(), value, conflicts);
471    }
472
473    @Override
474    @Deprecated
475    public double getWeightedValue(Collection<V> variables) {
476        return getWeightedValue(getModel().getDefaultAssignment(), variables);
477    }
478
479    @Override
480    @Deprecated
481    public double getValue(Collection<V> variables) {
482        return getValue(getModel().getDefaultAssignment(), variables);
483    }
484
485    @Override
486    @Deprecated
487    public double[] getBounds(Collection<V> variables) {
488        return getBounds(getModel().getDefaultAssignment(), variables);
489    }
490    
491    @Override
492    @Deprecated
493    public void inc(double value) {
494        inc(getModel().getDefaultAssignment(), value);
495    }
496    
497    /** Abbreviated name of the criterion for {@link TimetablingCriterion#toString()}. 
498     * @return abbreviated name of the criterion
499     **/
500    public String getAbbreviation() {
501        return getName().replaceAll("[a-z ]","");
502    }
503    
504    /**
505     * Simple text representation of the criterion and its value. E.g., X:x where X is the {@link AbstractCriterion#getAbbreviation()} 
506     * and x is the current value {@link AbstractCriterion#getValue(Assignment)}.
507     * @param assignment current assignment
508     * @return short string representation (e.g., PP:95% for period preference)
509     */
510    @Override
511    public String toString(Assignment<V, T> assignment) {
512        double val = getValue(assignment);
513        if (Math.abs(getWeightedValue(assignment)) < 0.005) return "";
514        double[] bounds = getBounds(assignment);
515        if (bounds[0] <= val && val <= bounds[1] && bounds[0] < bounds[1] && getName().endsWith(" Preferences"))
516            return getAbbreviation() + ":" + getPerc(val, bounds[0], bounds[1]) + "%";
517        else if (bounds[1] <= val && val <= bounds[0] && bounds[1] < bounds[0] && getName().endsWith(" Preferences"))
518            return getAbbreviation() + ":" + getPercRev(val, bounds[1], bounds[0]) + "%";
519        else if (bounds[0] != val || val != bounds[1])
520            return getAbbreviation() + ":" + sDoubleFormat.format(getValue(assignment));
521        else
522            return "";
523    }
524
525    public ValueUpdateType getValueUpdateType() {
526        return iValueUpdateType;
527    }
528
529    public void setValueUpdateType(ValueUpdateType iValueUpdateType) {
530        this.iValueUpdateType = iValueUpdateType;
531    }
532}