    001package org.cpsolver.ifs.solver;
    002
    003import java.util.ArrayList;
    004import java.util.Collection;
    005import java.util.HashMap;
    006import java.util.Iterator;
    007import java.util.List;
    008import java.util.Map;
    009import java.util.concurrent.ArrayBlockingQueue;
    010import java.util.concurrent.BlockingQueue;
    011import java.util.concurrent.TimeUnit;
    012import java.util.concurrent.locks.Lock;
    013
    014import org.cpsolver.ifs.assignment.Assignment;
    015import org.cpsolver.ifs.assignment.DefaultParallelAssignment;
    016import org.cpsolver.ifs.assignment.DefaultSingleAssignment;
    017import org.cpsolver.ifs.assignment.context.CanHoldContext;
    018import org.cpsolver.ifs.model.LazyNeighbour;
    019import org.cpsolver.ifs.model.LazyNeighbour.LazyNeighbourAcceptanceCriterion;
    020import org.cpsolver.ifs.model.Model;
    021import org.cpsolver.ifs.model.Neighbour;
    022import org.cpsolver.ifs.model.Value;
    023import org.cpsolver.ifs.model.Variable;
    024import org.cpsolver.ifs.solution.Solution;
    025import org.cpsolver.ifs.solution.SolutionListener;
    026import org.cpsolver.ifs.util.DataProperties;
    027import org.cpsolver.ifs.util.JProf;
    028import org.cpsolver.ifs.util.Progress;
    029import org.cpsolver.ifs.util.ToolBox;
    030
    031
    032/**
    033 * Multi-threaded solver. Instead of one, a given number of solver threads are created
    034 * (as defined by Parallel.NrSolvers property) and started in parallel. Each thread
    035 * works with its own assignment {@link DefaultParallelAssignment}, but the best solution
    036 * is shared among all of them.<br>
    037 * <br>
    038 * When {@link DefaultSingleAssignment} is given to the solver, only one solution is used.
    039 * A neighbour is assigned to this (shared) solution when it does not create any conflicts
    040 * outside of {@link Neighbour#assignments()}.
    041 *
    042 * @see Solver
    043 *
    044 * @version IFS 1.3 (Iterative Forward Search)<br>
    045 *          Copyright (C) 2014 Tomáš Müller<br>
    046 *          <a href="mailto:muller@unitime.org">muller@unitime.org</a><br>
    047 *          <a href="http://muller.unitime.org">http://muller.unitime.org</a><br>
    048 * <br>
    049 *          This library is free software; you can redistribute it and/or modify
    050 *          it under the terms of the GNU Lesser General Public License as
    051 *          published by the Free Software Foundation; either version 3 of the
    052 *          License, or (at your option) any later version. <br>
    053 * <br>
    054 *          This library is distributed in the hope that it will be useful, but
    055 *          WITHOUT ANY WARRANTY; without even the implied warranty of
    056 *          MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
    057 *          Lesser General Public License for more details. <br>
    058 * <br>
    059 *          You should have received a copy of the GNU Lesser General Public
    060 *          License along with this library; if not see <a href='http://www.gnu.org/licenses'>http://www.gnu.org/licenses</a>.
    061 *
    062 * @param <V> Variable
    063 * @param <T> Value
    064 **/
    065public class ParallelSolver<V extends Variable<V, T>, T extends Value<V, T>> extends Solver<V, T> {
    066    private SynchronizationThread iSynchronizationThread = null;
    067    private int iNrFinished = 0;
    068
    069    public ParallelSolver(DataProperties properties) {
    070        super(properties);
    071    }
    072
    073    /** Starts solver */
    074    @Override
    075    public void start() {
    076        int nrSolvers = Math.min(Math.abs(getProperties().getPropertyInt("Parallel.NrSolvers", 4)), CanHoldContext.sMaxSize - 1);
    077        if (nrSolvers == 1) {
    078            super.start();
    079        } else {
    080            iSynchronizationThread = new SynchronizationThread(nrSolvers);
    081            iSynchronizationThread.setPriority(THREAD_PRIORITY);
    082            iSynchronizationThread.start();
    083        }
    084    }
    085
    086    /** Returns solver's thread */
    087    @Override
    088    public Thread getSolverThread() {
    089        return iSynchronizationThread != null ? iSynchronizationThread : super.getSolverThread();
    090    }
    091
    092    /** Sets initial solution */
    093    @Override
    094    public void setInitalSolution(Model<V, T> model) {
    095        int nrSolvers = Math.min(Math.abs(getProperties().getPropertyInt("Parallel.NrSolvers", 4)), CanHoldContext.sMaxSize - 1);
    096        boolean updateMasterSolution = getProperties().getPropertyBoolean("Parallel.UpdateMasterSolution", true);
    097        setInitalSolution(new Solution<V, T>(model, nrSolvers > 1 ? new DefaultParallelAssignment<V, T>(updateMasterSolution ? 1 : 0) : new DefaultSingleAssignment<V, T>(), 0, 0));
    098    }
    099
    100    /**
    101     * Return a working (parallel) solution that contributed to the best solution last.
    102     * @return working solution
    103     */
    104    protected Solution<V, T> getWorkingSolution() {
    105        if (iSynchronizationThread != null && !hasSingleSolution()) {
    106            int idx = currentSolution().getBestIndex();
    107            if (idx < 0) idx = 0; // take the first thread solution if there was no best solution saved yet
    108            if (idx < iSynchronizationThread.iSolvers.size())
    109                return iSynchronizationThread.iSolvers.get(idx).iSolution;
    110        }
    111        return currentSolution();
    112    }
    113
    114    /**
    115     * Synchronization thread
    116     */
    117    protected class SynchronizationThread extends Thread {
    118        private int iNrSolvers;
    119        private List<SolverThread> iSolvers = new ArrayList<SolverThread>();
    120        private AssignmentThread iAssignmentThread = null;
    121
    122        SynchronizationThread(int nrSolvers) {
    123            iNrSolvers = nrSolvers;
    124        }
    125
    126        @Override
    127        public void run() {
    128            try {
    129                iStop = false;
    130                iNrFinished = 0;
    131                setName("SolverSync");
    132
    133                // Initialization
    134                iProgress = Progress.getInstance(currentSolution().getModel());
    135                iProgress.setStatus("Solving problem ...");
    136                iProgress.setPhase("Initializing solver");
    137                initSolver();
    138                onStart();
    139
    140                if (isUpdateProgress()) {
    141                    if (currentSolution().getBestInfo() == null) {
    142                        iProgress.setPhase("Searching for initial solution ...", currentSolution().getModel().variables().size());
    143                    } else {
    144                        iProgress.setPhase("Improving found solution ...");
    145                    }
    146                }
    147                sLogger.info("Initial solution:" + ToolBox.dict2string(currentSolution().getInfo(), 2));
    148                if ((iSaveBestUnassigned < 0 || iSaveBestUnassigned >= currentSolution().getAssignment().nrUnassignedVariables(currentSolution().getModel())) && (currentSolution().getBestInfo() == null || getSolutionComparator().isBetterThanBestSolution(currentSolution()))) {
    149                    if (currentSolution().getAssignment().nrAssignedVariables() == currentSolution().getModel().variables().size())
    150                        sLogger.info("Complete solution " + ToolBox.dict2string(currentSolution().getInfo(), 1) + " was found.");
    151                    currentSolution().saveBest();
    152                }
    153
    154                if (currentSolution().getModel().variables().isEmpty()) {
    155                    iProgress.error("Nothing to solve.");
    156                    iStop = true;
    157                }
    158
    159                BlockingQueue<Neighbour<V, T>> queue = null;
    160                if (hasSingleSolution() && iNrSolvers > 1 && getProperties().getPropertyBoolean("ParallelSolver.SingleSolutionQueue", false))
    161                    queue = new ArrayBlockingQueue<Neighbour<V, T>>(2 * iNrSolvers);
    162
    163                if (!iStop) {
    164                    for (int i = 1; i <= iNrSolvers; i++) {
    165                        SolverThread thread = new SolverThread(i, queue);
    166                        thread.setPriority(THREAD_PRIORITY);
    167                        thread.setName("Solver-" + i);
    168                        thread.start();
    169                        iSolvers.add(thread);
    170                    }
    171                }
    172
    173                if (queue != null) {
    174                    iAssignmentThread = new AssignmentThread(queue);
    175                    iAssignmentThread.start();
    176                }
    177
    178                int timeout = getProperties().getPropertyInt("Termination.TimeOut", 1800);
    179                double start = JProf.currentTimeSec();
    180                while (!iStop && iNrFinished < iNrSolvers) {
    181                    try {
    182                        Thread.sleep(1000);
    183                        double time = JProf.currentTimeSec() - start;
    184
    185                        // Increment progress bar
    186                        if (isUpdateProgress()) {
    187                            if (currentSolution().getBestInfo() != null && currentSolution().getModel().getBestUnassignedVariables() == 0) {
    188                                if (!"Improving found solution ...".equals(iProgress.getPhase()))
    189                                    iProgress.setPhase("Improving found solution ...");
    190                                iProgress.setProgress(Math.min(100, (int)Math.round(100 * time / timeout)));
    191                            } else if (currentSolution().getModel().getBestUnassignedVariables() > 0 && (currentSolution().getModel().countVariables() - currentSolution().getModel().getBestUnassignedVariables() > iProgress.getProgress())) {
    192                                iProgress.setProgress(currentSolution().getModel().countVariables() - currentSolution().getModel().getBestUnassignedVariables());
    193                            } else if (iSolvers.get(0).iAssignment.nrAssignedVariables() > iProgress.getProgress()) {
    194                                iProgress.setProgress(iSolvers.get(0).iAssignment.nrAssignedVariables());
    195                            }
    196                        }
    197                    } catch (InterruptedException e) {}
    198                }
    199
    200                boolean stop = iStop; iStop = true;
    201                for (SolverThread thread: iSolvers) {
    202                    try {
    203                        thread.join();
    204                    } catch (InterruptedException e) {}
    205                }
    206                if (iAssignmentThread != null) {
    207                    try {
    208                        iAssignmentThread.join();
    209                    } catch (InterruptedException e) {}
    210                }
    211
    212                // Finalization
    213                iLastSolution = iCurrentSolution;
    214
    215                iProgress.setPhase("Done", 1);
    216                iProgress.incProgress();
    217
    218                iStop = stop;
    219                if (iStop) {
    220                    sLogger.debug("Solver stopped.");
    221                    iProgress.setStatus("Solver stopped.");
    222                    onStop();
    223                } else {
    224                    sLogger.debug("Solver done.");
    225                    iProgress.setStatus("Solver done.");
    226                    onFinish();
    227                }
    228            } catch (Exception ex) {
    229                sLogger.error(ex.getMessage(), ex);
    230                iProgress.fatal("Solver synchronization failed, reason:" + ex.getMessage(), ex);
    231                iProgress.setStatus("Solver failed.");
    232                onFailure();
    233            } finally {
    234                iSynchronizationThread = null;
    235            }
    236        }
    237    }
    238
    239    /**
    240     * Create a solution that is to be used by a solver thread of the given index
    241     * @param index solver thread index
    242     * @return new solution to work with
    243     */
    244    protected Solution<V, T> createParallelSolution(int index) {
    245        Model<V, T> model = iCurrentSolution.getModel();
    246        Assignment<V, T> assignment = new DefaultParallelAssignment<V, T>(index, model, iCurrentSolution.getAssignment());
    247        model.createAssignmentContexts(assignment, true);
    248        Solution<V, T> solution = new Solution<V, T>(model, assignment);
    249        for (SolutionListener<V, T> listener: iCurrentSolution.getSolutionListeners())
    250            solution.addSolutionListener(listener);
    251        return solution;
    252    }
    253
    254    /**
    255     * Returns true if the solver works only with one solution (regardless the number of threads it is using)
    256     * @return true if the current solution is {@link DefaultSingleAssignment}
    257     */
    258    @Override
    259    public boolean hasSingleSolution() {
    260        return iCurrentSolution.getAssignment() instanceof DefaultSingleAssignment;
    261    }
    262
    263    /**
    264     * Solver thread
    265     */
    266    protected class SolverThread extends Thread {
    267        private double iStartTime;
    268        private int iIndex;
    269        private boolean iSingle;
    270        private Model<V, T> iModel;
    271        private Solution<V, T> iSolution;
    272        private Assignment<V, T> iAssignment;
    273        private BlockingQueue<Neighbour<V, T>> iQueue;
    274
    275        public SolverThread(int index, BlockingQueue<Neighbour<V, T>> queue) {
    276            iIndex = index;
    277            iSingle = hasSingleSolution();
    278            iModel = iCurrentSolution.getModel();
    279            iSolution = (iSingle || iCurrentSolution.getAssignment().getIndex() == index ? iCurrentSolution : createParallelSolution(iIndex));
    280            iAssignment = iSolution.getAssignment();
    281            iQueue = queue;
    282        }
    283
    284        @Override
    285        public void run() {
    286            iStartTime = JProf.currentTimeSec();
    287            try {
    288                boolean neighbourCheck = getProperties().getPropertyBoolean("ParallelSolver.SingleSolutionNeighbourCheck", false);
    289                boolean tryLazyFirst = getProperties().getPropertyBoolean("ParallelSolver.SingleSolutionTryLazyFirst", false);
    290
    291                while (!iStop) {
    292                    // Break if cannot continue
    293                    if (!getTerminationCondition().canContinue(iSolution)) break;
    294
    295                    // Create a sub-solution if needed
    296                    Solution<V, T> current = iSolution;
    297                    if (iSingle) {
    298                        current = new Solution<V, T>(iModel, iModel.createInheritedAssignment(iSolution, iIndex), iSolution.getIteration(), iSolution.getTime());
    299                        current.addSolutionListener(new SolutionListener<V, T>() {
    300                            @Override
    301                            public void solutionUpdated(Solution<V, T> solution) {
    302                            }
    303
    304                            @Override
    305                            public void getInfo(Solution<V, T> solution, Map<String, String> info) {
    306                            }
    307
    308                            @Override
    309                            public void getInfo(Solution<V, T> solution, Map<String, String> info, Collection<V> variables) {
    310                            }
    311
    312                            @Override
    313                            public void bestCleared(Solution<V, T> solution) {
    314                            }
    315
    316                            @Override
    317                            public void bestSaved(Solution<V, T> solution) {
    318                            }
    319
    320                            @Override
    321                            public void bestRestored(Solution<V, T> solution) {
    322                                iSolution.restoreBest();
    323                            }
    324                        });
    325                    }
    326
    327                    // Neighbour selection
    328                    Neighbour<V, T> neighbour = null;
    329                    try {
    330                        neighbour = getNeighbourSelection().selectNeighbour(current);
    331                    } catch (Exception e) {
    332                        sLogger.debug("Failed to select a neighbour: " + (e.getMessage() == null ? e.getClass().getSimpleName() : e.getMessage()), e);
    333                    }
    334                    for (SolverListener<V, T> listener : iSolverListeners) {
    335                        if (!listener.neighbourSelected(iAssignment, iSolution.getIteration(), neighbour)) {
    336                            neighbour = null;
    337                            continue;
    338                        }
    339                    }
    340
    341                    double time = JProf.currentTimeSec() - iStartTime;
    342                    if (neighbour == null) {
    343                        sLogger.debug("No neighbour selected.");
    344                        // still update the solution (increase iteration etc.)
    345                        iSolution.update(time, false);
    346                        continue;
    347                    }
    348
    349                    if (iSingle) {
    350                        if (iQueue != null) {
    351                            do {
    352                                if (iQueue.offer(neighbour, 1000, TimeUnit.MILLISECONDS)) break;
    353                            } while (!iStop && getTerminationCondition().canContinue(iSolution));
    354                            continue;
    355                        }
    356
    357                        Map<V, T> assignments = null;
    358                        try {
    359                            assignments = neighbour.assignments();
    360                        } catch (Exception e) {
    361                            sLogger.error("Failed to enumerate " + neighbour.getClass().getSimpleName(), e);
    362                        }
    363                        if (assignments == null) {
    364                            sLogger.debug("No assignments returned.");
    365                            // still update the solution (increase iteration etc.)
    366                            iSolution.update(time, false);
    367                            continue;
    368                        }
    369
    370                        if (tryLazyFirst && neighbour instanceof LazyNeighbour) {
    371                            LazyNeighbour<V, T> lazy = (LazyNeighbour<V, T>)neighbour;
    372                            double before = current.getModel().getTotalValue(current.getAssignment());
    373                            neighbour.assign(current.getAssignment(), current.getIteration());
    374                            double after = current.getModel().getTotalValue(current.getAssignment());
    375                            if (!lazy.getAcceptanceCriterion().accept(current.getAssignment(), lazy, after - before))
    376                                continue;
    377                        }
    378
    379                        // Assign selected value to the selected variable
    380                        Lock lock = iSolution.getLock().writeLock();
    381                        lock.lock();
    382                        try {
    383                            LazyNeighbourAcceptanceCriterion<V,T> lazy = null;
    384                            double before = 0, value = 0;
    385                            if (neighbour instanceof LazyNeighbour) {
    386                                before = iSolution.getModel().getTotalValue(iSolution.getAssignment());
    387                                lazy = ((LazyNeighbour<V, T>)neighbour).getAcceptanceCriterion();
    388                            } else if (neighbourCheck) {
    389                                before = iSolution.getModel().getTotalValue(iSolution.getAssignment());
    390                                value = neighbour.value(current.getAssignment());
    391                            }
    392                            Map<V, T> undo = new HashMap<V, T>();
    393                            for (Iterator<Map.Entry<V, T>> i = assignments.entrySet().iterator(); i.hasNext(); ) {
    394                                Map.Entry<V, T> e = i.next();
    395                                T cur = iSolution.getAssignment().getValue(e.getKey());
    396                                if (e.getValue() == null && cur == null) {
    397                                    i.remove();
    398                                } else if (cur != null && cur.equals(e.getValue())) {
    399                                    i.remove();
    400                                } else {
    401                                    undo.put(e.getKey(), iSolution.getAssignment().unassign(iSolution.getIteration(), e.getKey()));
    402                                }
    403                            }
    404                            boolean fail = false;
    405                            for (T val: assignments.values()) {
    406                                if (val == null) continue;
    407                                if (iModel.inConflict(iSolution.getAssignment(), val)) {
    408                                    fail = true; break;
    409                                }
    410                                iSolution.getAssignment().assign(iSolution.getIteration(), val);
    411                            }
    412                            if (!fail) {
    413                                if (lazy != null) {
    414                                    double after = iSolution.getModel().getTotalValue(iSolution.getAssignment());
    415                                    if (!lazy.accept(iSolution.getAssignment(), (LazyNeighbour<V, T>) neighbour, after - before))
    416                                        fail = true;
    417                                } else if (neighbourCheck) {
    418                                    double after = iSolution.getModel().getTotalValue(iSolution.getAssignment());
    419                                    if (before + value < after && before < after && !getSolutionComparator().isBetterThanBestSolution(iSolution))
    420                                        fail = true;
    421                                }
    422                            }
    423                            if (fail) {
    424                                for (V var: undo.keySet())
    425                                    iSolution.getAssignment().unassign(iSolution.getIteration(), var);
    426                                for (T val: undo.values())
    427                                    if (val != null)
    428                                        iSolution.getAssignment().assign(iSolution.getIteration(), val);
    429                            }
    430                            iSolution.update(time, !fail);
    431                            if (fail) {
    432                                for (SolverListener<V, T> listener : iSolverListeners)
    433                                    listener.neighbourFailed(current.getAssignment(), iSolution.getIteration(), neighbour);
    434                                continue;
    435                            }
    436
    437                            onAssigned(iStartTime, iSolution);
    438
    439                            if ((iSaveBestUnassigned < 0 || iSaveBestUnassigned >= iSolution.getAssignment().nrUnassignedVariables(iModel)) && getSolutionComparator().isBetterThanBestSolution(iSolution)) {
    440                                iSolution.saveBest();
    441                            }
    442                        } finally {
    443                            lock.unlock();
    444                        }
    445                    } else {
    446                        // Assign selected value to the selected variable
    447                        Lock lock = iSolution.getLock().writeLock();
    448                        lock.lock();
    449                        try {
    450                            neighbour.assign(iAssignment, iSolution.getIteration());
    451                            iSolution.update(time, currentSolution());
    452                        } finally {
    453                            lock.unlock();
    454                        }
    455
    456                        onAssigned(iStartTime, iSolution);
    457
    458                        if (iSaveBestUnassigned < 0 || iSaveBestUnassigned >= iAssignment.nrUnassignedVariables(iModel))
    459                            iSolution.saveBestIfImproving(currentSolution(), getSolutionComparator());
    460                    }
    461                }
    462
    463            } catch (Exception ex) {
    464                sLogger.error(ex.getMessage(), ex);
    465                iProgress.fatal(getName() + " failed, reason:" + ex.getMessage(), ex);
    466                if (iIndex == 0) {
    467                    iProgress.setStatus("Solver failed.");
    468                    onFailure();
    469                }
    470            }
    471            Lock lock = currentSolution().getLock().writeLock();
    472            lock.lock();
    473            try {
    474                iNrFinished ++;
    475            } finally {
    476                lock.unlock();
    477            }
    478        }
    479
    480    }
    481
    482    /**
    483     * Solver thread
    484     */
    485    protected class AssignmentThread extends Thread {
    486        private double iStartTime;
    487        private Solution<V, T> iSolution;
    488        private BlockingQueue<Neighbour<V, T>> iQueue;
    489
    490        public AssignmentThread(BlockingQueue<Neighbour<V, T>> queue) {
    491            setName("Assignment");
    492            setPriority(1 + THREAD_PRIORITY);
    493            iSolution = iCurrentSolution;
    494            iQueue = queue;
    495        }
    496
    497        @Override
    498        public void run() {
    499            iStartTime = JProf.currentTimeSec();
    500            try {
    501                boolean neighbourCheck = getProperties().getPropertyBoolean("ParallelSolver.SingleSolutionNeighbourCheck", false);
    502
    503                while (!iStop) {
    504                    // Break if cannot continue
    505                    if (!getTerminationCondition().canContinue(iSolution)) break;
    506
    507                    // Create a sub-solution if needed
    508                    Neighbour<V, T> neighbour = iQueue.poll(1000, TimeUnit.MILLISECONDS);
    509
    510                    if (neighbour == null) continue;
    511
    512                    double time = JProf.currentTimeSec() - iStartTime;
    513
    514                    Map<V, T> assignments = null;
    515                    try {
    516                        assignments = neighbour.assignments();
    517                    } catch (Exception e) {
    518                        sLogger.error("Failed to enumerate " + neighbour.getClass().getSimpleName(), e);
    519                    }
    520                    if (assignments == null) {
    521                        sLogger.debug("No assignments returned.");
    522                        // still update the solution (increase iteration etc.)
    523                        iSolution.update(time, false);
    524                        continue;
    525                    }
    526
    527                    // Assign selected value to the selected variable
    528                    Lock lock = iSolution.getLock().writeLock();
    529                    lock.lock();
    530                    try {
    531                        LazyNeighbourAcceptanceCriterion<V,T> lazy = null;
    532                        double before = 0, value = 0;
    533                        if (neighbour instanceof LazyNeighbour) {
    534                            before = iSolution.getModel().getTotalValue(iSolution.getAssignment());
    535                            lazy = ((LazyNeighbour<V, T>)neighbour).getAcceptanceCriterion();
    536                        } else if (neighbourCheck) {
    537                            before = iSolution.getModel().getTotalValue(iSolution.getAssignment());
    538                            value = neighbour.value(iSolution.getAssignment());
    539                        }
    540                        Map<V, T> undo = new HashMap<V, T>();
    541                        for (V var: assignments.keySet())
    542                            undo.put(var, iSolution.getAssignment().unassign(iSolution.getIteration(), var));
    543                        boolean fail = false;
    544                        for (T val: assignments.values()) {
    545                            if (val == null) continue;
    546                            if (iSolution.getModel().inConflict(iSolution.getAssignment(), val)) {
    547                                fail = true; break;
    548                            }
    549                            iSolution.getAssignment().assign(iSolution.getIteration(), val);
    550                        }
    551                        if (!fail) {
    552                            if (lazy != null) {
    553                                double after = iSolution.getModel().getTotalValue(iSolution.getAssignment());
    554                                if (!lazy.accept(iSolution.getAssignment(), (LazyNeighbour<V, T>) neighbour, after - before))
    555                                    fail = true;
    556                            } else if (neighbourCheck) {
    557                                double after = iSolution.getModel().getTotalValue(iSolution.getAssignment());
    558                                if (before + value < after && before < after && !getSolutionComparator().isBetterThanBestSolution(iSolution))
    559                                    fail = true;
    560                            }
    561                        }
    562                        if (fail) {
    563                            for (V var: undo.keySet())
    564                                iSolution.getAssignment().unassign(iSolution.getIteration(), var);
    565                            for (T val: undo.values())
    566                                if (val != null)
    567                                    iSolution.getAssignment().assign(iSolution.getIteration(), val);
    568                        }
    569                        iSolution.update(time, !fail);
    570                        if (fail) {
    571                            for (SolverListener<V, T> listener : iSolverListeners)
    572                                listener.neighbourFailed(iSolution.getAssignment(), iSolution.getIteration(), neighbour);
    573                            continue;
    574                        }
    575
    576                        onAssigned(iStartTime, iSolution);
    577
    578                        if ((iSaveBestUnassigned < 0 || iSaveBestUnassigned >= iSolution.getAssignment().nrUnassignedVariables(iSolution.getModel())) && getSolutionComparator().isBetterThanBestSolution(iSolution)) {
    579                            iSolution.saveBest();
    580                        }
    581                    } finally {
    582                        lock.unlock();
    583                    }
    584                }
    585
    586            } catch (Exception ex) {
    587                sLogger.error(ex.getMessage(), ex);
    588                iProgress.fatal(getName() + " failed, reason:" + ex.getMessage(), ex);
    589            }
    590            Lock lock = currentSolution().getLock().writeLock();
    591            lock.lock();
    592            try {
    593                iNrFinished ++;
    594            } finally {
    595                lock.unlock();
    596            }
    597        }
    598
    599    }
    600
    601}