aboutsummaryrefslogtreecommitdiffstats
path: root/subprojects/viatra-runtime-localsearch/src/main/java/tools/refinery/viatra/runtime/localsearch/planner/PlanState.java
blob: e93b07bc403be31db4636eab4da548eb237d8e98 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
/*******************************************************************************
 * Copyright (c) 2010-2015, Marton Bur, Zoltan Ujhelyi, Akos Horvath, Istvan Rath and Daniel Varro
 * This program and the accompanying materials are made available under the
 * terms of the Eclipse Public License v. 2.0 which is available at
 * http://www.eclipse.org/legal/epl-v20.html.
 * 
 * SPDX-License-Identifier: EPL-2.0
 *******************************************************************************/
package tools.refinery.viatra.runtime.localsearch.planner;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

import tools.refinery.viatra.runtime.localsearch.planner.util.OperationCostComparator;
import tools.refinery.viatra.runtime.matchers.algorithms.OrderedIterableMerge;
import tools.refinery.viatra.runtime.matchers.psystem.PBody;
import tools.refinery.viatra.runtime.matchers.psystem.PConstraint;
import tools.refinery.viatra.runtime.matchers.psystem.PVariable;

/**
 * This class represents the state of the plan during planning.
 * 
 * <p> A PlanState represents a sequence of operations (operationsList) and caches the computed cost 
 * for this operation sequence. The list and the cost are initialized in the constructor. 
 * However, #categorizeChecks() also updates the operations list (by suffixing checks)
 * 
 * @author Marton Bur
 * @noreference This class is not intended to be referenced by clients.
 */
public class PlanState {

    private final PBody pBody;
    private final List<PConstraintInfo> operationsList;
    private final Set<PVariable> boundVariables;
    private final Collection<PVariable> deltaVariables; /* bound since ancestor plan */
    private final Set<PConstraint> enforcedConstraints;
    
    private double cummulativeProduct;
    private double cost;

    private static Comparator<PConstraintInfo> infoComparator = new OperationCostComparator();

    /*
     * For a short explanation of past, present and future operations,
     * see class 
     */
    private List<PConstraintInfo> presentExtends;

    /**
     * Creates an initial state
     */
    public PlanState(PBody pBody, Set<PVariable> boundVariables) {

        this(pBody, new ArrayList<>(), boundVariables, boundVariables /* also the delta */, 
                0.0 /* initial cost */, 1.0 /*initial branch count */);
    }
    
    public PlanState cloneWithApplied(PConstraintInfo op) {
        // Create operation list based on the current state
        ArrayList<PConstraintInfo> newOperationsList =  
                // pre-reserve excess capacity for later addition of CHECK ops
                new ArrayList<>(pBody.getConstraints().size());
        newOperationsList.addAll(this.getOperations());
        newOperationsList.add(op);
        
        // Bind the variables of the op
        Collection<PVariable> deltaVariables = op.getFreeVariables();
        Set<PVariable> allBoundVariables =
                // pre-reserve exact capacity as variables are known 
                // (will not be affected by adding CHECK ops later)
                new HashSet<>(this.getBoundVariables().size() + deltaVariables.size());
        allBoundVariables.addAll(this.getBoundVariables());
        allBoundVariables.addAll(deltaVariables);
        
        PlanState newState = new PlanState(getAssociatedPBody(), newOperationsList, allBoundVariables, deltaVariables,
                cost, cummulativeProduct);
        newState.accountNewOperation(op);
        return newState;
    }

    private PlanState(PBody pBody, List<PConstraintInfo> operationsList, 
            Set<PVariable> boundVariables, Collection<PVariable> deltaVariables, 
            double cost, double cummulativeProduct) 
    {
        this.pBody = pBody;
        this.operationsList = operationsList;
        this.boundVariables = boundVariables;
        this.enforcedConstraints = new HashSet<>();
        this.deltaVariables = deltaVariables;
        this.cost = cost;
        this.cummulativeProduct = cummulativeProduct;
    }

    // NOT included for EXTEND: bind all variables of op
    private void accountNewOperation(PConstraintInfo constraintInfo) {
        this.enforcedConstraints.add(constraintInfo.getConstraint());
        accountCost(constraintInfo);
    }

    private void accountCost(PConstraintInfo constraintInfo) {
        double constraintCost = constraintInfo.getCost();
        double branchFactor = constraintCost;
        if (constraintCost > 0){
            cost += cummulativeProduct * constraintCost;
            cummulativeProduct *= branchFactor;
        }
    }


    public Set<PConstraint> getEnforcedConstraints() {
        return enforcedConstraints;
    }

    /**
     * Re-categorizes given extend operations into already applied or no longer applicable ones (discarded), 
     * immediately applicable ones (saved as presently viable extends), 
     * and not yet applicable ones (discarded).
     * 
     * @param allPotentialExtendInfos all other extends that may be applicable 
     * to this plan state now or in the future;
     * MUST consist of "extend" constraint applications only (at least one free variable)
     */
    public void updateExtends(Iterable<PConstraintInfo> allPotentialExtendInfos) {
        presentExtends = new ArrayList<>();
        
        
        // categorize future/present extend constraint infos
        for (PConstraintInfo op : allPotentialExtendInfos) {
            updateExtendInternal(op);
        }
    }

    /**
     * Re-categorizes given extend operations into already applied or no longer applicable ones (discarded), 
     * immediately applicable ones (saved as presently viable extends),
     * and not yet applicable ones (discarded).
     * 
     * @param extendOpsByBoundVariables all EXTEND operations indexed by affected <i>bound</i> variables
     * MUST consist of "extend" constraint applications only (at least one free variable)
     */
    public void updateExtendsBasedOnDelta(
            Iterable<PConstraintInfo> previousPresentExtends, 
            Map<PVariable, ? extends Collection<PConstraintInfo>> extendOpsByBoundVariables) 
    {
        presentExtends = new ArrayList<>();
        if (operationsList.isEmpty())
            throw new IllegalStateException("Not applicable as starting step");     
        
        for (PConstraintInfo extend: previousPresentExtends) {
            updateExtendInternal(extend);
        }
        
        Set<PConstraintInfo> affectedExtends = new HashSet<>();
        for (PVariable variable : deltaVariables) {
            // only those check ops may become applicable that have an affected variable in the delta
            Collection<PConstraintInfo> extendsForVariable = extendOpsByBoundVariables.get(variable);
            if (null != extendsForVariable) {
                affectedExtends.addAll(extendsForVariable);
            }
        }
        for (PConstraintInfo extend: affectedExtends) {
            updateExtendInternal(extend);
        }
    }
    
    private void updateExtendInternal(PConstraintInfo op) {
        if(!enforcedConstraints.contains(op.getConstraint())) {
            categorizeExtend(op);                
        }
    }
    
    /**
     * Check operations that newly became applicable (see {@link #getDeltaVariables()}) 
     * are appended to operations lists.
     * 
     * <p> Will never discover degenerate checks (of PConstraints with zero variables),
     * so must not use on initial state.
     * 
     * @param allPotentialCheckInfos all CHECK operations
     * MUST consist of "check" constraint applications only (no free variables) 
     * and must be iterable in decreasing order of cost
     * 
     * 
     */
    public void applyChecks(List<PConstraintInfo> allPotentialCheckInfos) {
        applyChecksInternal(allPotentialCheckInfos);        
    }
 
    /**
     * Immediately applicable checks are appended to operations lists.
     * 
     * @param checkOpsByVariables all CHECK operations indexed by affected variables
     * MUST consist of "check" constraint applications only (no free variables) 
     * and each bucket must be iterable in decreasing order of cost
     */
    public void applyChecksBasedOnDelta(Map<PVariable, List<PConstraintInfo>> checkOpsByVariables) {
        if (operationsList.isEmpty())
            throw new IllegalStateException("Not applicable as starting step");     
        
        Iterable<PConstraintInfo> affectedChecks = Collections.emptyList();
        
        for (PVariable variable : deltaVariables) {
            // only those check ops may become applicable that have an affected variable in the delta
            List<PConstraintInfo> checksForVariable = checkOpsByVariables.get(variable);
            if (null != checksForVariable) {
                affectedChecks = OrderedIterableMerge.mergeUniques(affectedChecks, checksForVariable, infoComparator);
            }
        }
        
        // checks retain their order, no re-sorting needed
        applyChecksInternal(affectedChecks);
    }

    private void applyChecksInternal(Iterable<PConstraintInfo> checks) {
        for (PConstraintInfo checkInfo : checks) {
            if (this.boundVariables.containsAll(checkInfo.getBoundVariables()) &&
                    !enforcedConstraints.contains(checkInfo.getConstraint())) 
            {
                operationsList.add(checkInfo);
                accountNewOperation(checkInfo);                    
            }
        }
    }


    private void categorizeExtend(PConstraintInfo constraintInfo) {
        PConstraintCategory category = constraintInfo.getCategory(pBody, boundVariables);
        if (category == PConstraintCategory.PRESENT) {
            presentExtends.add(constraintInfo);
        } else {
            // do not categorize past/future operations
        }
    }


    public PBody getAssociatedPBody() {
        return pBody;
    }

    public List<PConstraintInfo> getOperations() {
        return operationsList;
    }

    public Set<PVariable> getBoundVariables() {
        return boundVariables;
    }

    /**
     * @return the derived cost of the plan contained in the state
     */
    public double getCost() {
        return cost;
    }

    
    /**
     * @return cumulative branching factor
     * @since 2.1
     */
    public double getCummulativeProduct() {
        return cummulativeProduct;
    }

    public List<PConstraintInfo> getPresentExtends() {
        return presentExtends;
    }

    /**
     * Contains only those variables that are added by the newest extend 
     * (or the initially bound ones if no extend yet)
     */
    public Collection<PVariable> getDeltaVariables() {
        return deltaVariables;
    }

    
}