All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.chocosolver.solver.ModelAnalyser Maven / Gradle / Ivy

The newest version!
/*
 * This file is part of choco-solver, http://choco-solver.org/
 *
 * Copyright (c) 2024, IMT Atlantique. All rights reserved.
 *
 * Licensed under the BSD 4-clause license.
 *
 * See LICENSE file in the project root for full license information.
 */
package org.chocosolver.solver;

import org.chocosolver.solver.constraints.Constraint;
import org.chocosolver.solver.constraints.Propagator;
import org.chocosolver.solver.variables.*;
import org.chocosolver.util.ESat;
import org.chocosolver.util.logger.Logger;

import java.io.IOException;
import java.io.PrintStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import static java.nio.file.StandardOpenOption.APPEND;

/**
 * The ModelAnalyser is a class providing methods to analyse the Model.
 * Most especially, it gives tools to analyse the list of Variable and the list of Propagator
 * of the Model depending on their types and class.
 *
 * @author Arthur Godet
 * @see org.chocosolver.solver.Model
 * @see org.chocosolver.solver.variables.Variable
 * @see org.chocosolver.solver.constraints.Constraint
 */
public class ModelAnalyser {
    private final Model model;

    private static final Class[] VARS_TYPES = new Class[]{BoolVar.class, GraphVar.class, IntVar.class, RealVar.class, SetVar.class};
    private final Map>> mapTypeClassVars = new HashMap<>();
    private final Map>> mapTypeClassCstrs = new HashMap<>();

    /**
     * Creates a ModelAnalyser for the given Model
     * @param model the Model
     * @see Model
     */
    public ModelAnalyser(Model model) {
        this.model = model;
    }

    /**
     * Get the Model analysed by this ModelAnalyser.
     * @return the model
     * @see Model
     */
    public Model getModel() {
        return model;
    }

    private static String getClassName(Class c) {
        String[] sp = c.toString().split("\\.");
        return sp[sp.length - 1];
    }

    private void retrieveVariableData() {
        mapTypeClassVars.clear();
        for (Class c : VARS_TYPES) {
            Map> typeMap = Arrays.stream(model.getVars())
                    .filter(Objects::nonNull)
                    .filter(c::isInstance)
                    .filter(var -> !c.equals(IntVar.class) || !(var instanceof BoolVar)) // to distinct BoolVar from IntVar
                    .collect(Collectors.groupingBy(var -> getClassName(var.getClass())));
            if (!typeMap.isEmpty()) {
                mapTypeClassVars.put(getClassName(c), typeMap);
            }
        }
    }

    private void retrievePropagatorData() {
        String[] cstrsTypes = Arrays.stream(model.getCstrs()).filter(Objects::nonNull).map(Constraint::getName).distinct().toArray(String[]::new);
        mapTypeClassCstrs.clear();
        for (String cstrName : cstrsTypes) {
            Map> typeMap = Arrays.stream(model.getCstrs())
                    .filter(Objects::nonNull)
                    .filter(cstr -> cstrName.equals(cstr.getName()))
                    .flatMap(cstr -> Arrays.stream(cstr.getPropagators()))
                    .collect(Collectors.groupingBy(var -> getClassName(var.getClass())));
            if (!typeMap.isEmpty()) {
                mapTypeClassCstrs.put(cstrName, typeMap);
            }
        }
    }

    private void retrieveModelData() {
        // Retrieve Variable data
        retrieveVariableData();
        // Retrieve Constraint and Propagator data
        retrievePropagatorData();
    }

    /**
     * Analyse the variables of the Model of this
     *
     * @return the VariableTypeStatistics resulting of the variables analysis
     * @see VariableTypeStatistics
     * @see ModelAnalyser#getVariableTypes()
     * @see ModelAnalyser#getVariableClassNamesOfType(String)
     */
    public VariableTypeStatistics[] analyseVariables() {
        retrieveVariableData();
        return getVariableTypes().stream()
                .map(c -> getVariableClassNamesOfType(c).stream().map(varType -> createVariableTypeStatistics(c, varType)).collect(Collectors.toList()))
                .flatMap(List::stream)
                .toArray(VariableTypeStatistics[]::new);
    }

    /**
     * Analyse the propagators of the Model of this
     *
     * @return the ConstraintTypeStatistics resulting of the propagators analysis
     * @see ConstraintTypeStatistics
     * @see ModelAnalyser#getConstraintTypes()
     * @see ModelAnalyser#getConstraintClassNamesOfType(String)
     */
    public ConstraintTypeStatistics[] analyseConstraints() {
        retrievePropagatorData();
        return getConstraintTypes().stream()
                .map(c -> getConstraintClassNamesOfType(c).stream().map(propType -> createConstraintTypeStatistics(c, propType)).collect(Collectors.toList()))
                .flatMap(List::stream)
                .toArray(ConstraintTypeStatistics[]::new);
    }

    /**
     * Analyse the Model of this
     *
     * @return the ModelAnalysis resulting of the model analysis
     */
    public ModelAnalysis analyseModel() {
        VariableTypeStatistics[] varsTypeStats = analyseVariables();
        ConstraintTypeStatistics[] cstrsTypeStats = analyseConstraints();
        return new ModelAnalysis(varsTypeStats, cstrsTypeStats);
    }

    /**
     * Return the list of Variable types present in the Model.
     * Method retrieveVariableData() must have been called prior to this for it to work.
     *
     * @return list of variables types
     * @see ModelAnalyser#VARS_TYPES
     */
    public List getVariableTypes() {
        return mapTypeClassVars.keySet().stream().filter(c -> !mapTypeClassVars.get(c).isEmpty())
                .sorted()
                .collect(Collectors.toList());
    }

    /**
     * Return the list of Variable classes present in the Model for the given variable type.
     * Method retrieveVariableData() must have been called prior to this for it to work.
     *
     * @param variableType the type of the variables
     * @return list of variables classes
     * @see ModelAnalyser#VARS_TYPES
     */
    public List getVariableClassNamesOfType(String variableType) {
        if (mapTypeClassVars.containsKey(variableType)) {
            return mapTypeClassVars.get(variableType).keySet().stream().sorted().collect(Collectors.toList());
        }
        return new ArrayList<>();
    }

    private List retrieveVariablesWithProperty(Predicate predicate) {
        return Arrays.stream(this.model.getVars())
                .filter(predicate)
                .collect(Collectors.toList());
    }

    private List getVariablesWithPropertyOfType(Predicate predicate, String type) {
        return this.mapTypeClassVars.computeIfAbsent(type, s -> new HashMap<>())
                .values()
                .stream()
                .flatMap(List::stream)
                .filter(predicate)
                .collect(Collectors.toList());
    }

    /**
     * Return the list of unconstrained variables in the Model
     *
     * @return the list of unconstrained variables
     */
    public List getUnconstrainedVariables() {
        return retrieveVariablesWithProperty(v -> v.getNbProps() == 0);
    }

    /**
     * Return the list of unconstrained variables in the Model of the given type
     * Method retrieveVariableData() must have been called prior to this for it to work.
     *
     * @return the list of unconstrained variables of the given type
     * @see ModelAnalyser#getVariableTypes()
     */
    public List getUnconstrainedVariables(String type) {
        return getVariablesWithPropertyOfType(v -> v.getNbProps() == 0, type);
    }

    /**
     * Return the list of variables with at least one view in the Model
     *
     * @return the list of variables with at least one view
     */
    public List getVariablesWithViews() {
        return retrieveVariablesWithProperty(v -> v.getNbViews() > 0);
    }

    /**
     * Return the list of variables with at least one view in the Model of the given type
     * Method retrieveVariableData() must have been called prior to this for it to work.
     *
     * @return the list of variables of the given type with at least one view
     * @see ModelAnalyser#getVariableTypes()
     */
    public List getVariablesWithViews(String type) {
        return getVariablesWithPropertyOfType(v -> v.getNbViews() > 0, type);
    }

    /**
     * Return the list of Constraint types present in the Model.
     * Method retrievePropagatorData() must have been called prior to this for it to work.
     *
     * @return list of constraints types
     * @see Constraint#getName()
     */
    public List getConstraintTypes() {
        return mapTypeClassCstrs.keySet().stream().filter(c -> !mapTypeClassCstrs.get(c).isEmpty())
                .sorted()
                .collect(Collectors.toList());
    }

    /**
     * Return the list of Propagator types present in the Model for the given Constraint type.
     * Method retrievePropagatorData() must have been called prior to this for it to work.
     *
     * @param constraintType the type of the Constraint
     * @return list of propagators classes
     * @see Constraint#getName()
     * @see ModelAnalyser#getConstraintTypes()
     */
    public List getConstraintClassNamesOfType(String constraintType) {
        if (mapTypeClassCstrs.containsKey(constraintType)) {
            return mapTypeClassCstrs.get(constraintType).keySet().stream().sorted().collect(Collectors.toList());
        }
        return new ArrayList<>();
    }

    private List retrievePropagatorWithProperty(Predicate predicate) {
        return Arrays.stream(this.model.getCstrs())
                .map(Constraint::getPropagators)
                .flatMap(Arrays::stream)
                .filter(predicate)
                .collect(Collectors.toList());
    }

    private List getPropagatorsWithPropertyOfType(Predicate predicate, String type) {
        return this.mapTypeClassCstrs.computeIfAbsent(type, s -> new HashMap<>())
                .values()
                .stream()
                .flatMap(List::stream)
                .filter(predicate)
                .collect(Collectors.toList());
    }

    /**
     * Return the list of entailed propagators in the Model
     *
     * @return the list of entailed propagators
     */
    public List getEntailedPropagators() {
        return retrievePropagatorWithProperty(p -> p.isEntailed().equals(ESat.TRUE));
    }

    /**
     * Return the list of entailed propagators in the Model of the given type
     * Method retrievePropagatorData() must have been called prior to this for it to work.
     *
     * @return the list of entailed propagators of the given type
     * @see ModelAnalyser#getConstraintTypes()
     */
    public List getEntailedPropagators(String type) {
        return getPropagatorsWithPropertyOfType(p -> p.isEntailed().equals(ESat.TRUE), type);
    }

    /**
     * Return the list of passive propagators in the Model
     *
     * @return the list of passive propagators
     */
    public List getPassivePropagators() {
        return retrievePropagatorWithProperty(Propagator::isPassive);
    }

    /**
     * Return the list of passive propagators in the Model of the given type
     * Method retrievePropagatorData() must have been called prior to this for it to work.
     *
     * @return the list of passive propagators of the given type
     * @see ModelAnalyser#getConstraintTypes()
     */
    public List getPassivePropagators(String type) {
        return getPropagatorsWithPropertyOfType(Propagator::isPassive, type);
    }

    /**
     * Return the list of completely instantiated propagators in the Model
     *
     * @return the list of completely instantiated propagators
     */
    public List getCompletelyInstantiatedPropagators() {
        return retrievePropagatorWithProperty(Propagator::isCompletelyInstantiated);
    }

    /**
     * Return the list of completely instantiated propagators in the Model of the given type
     * Method retrievePropagatorData() must have been called prior to this for it to work.
     *
     * @return the list of completely instantiated propagators of the given type
     * @see ModelAnalyser#getConstraintTypes()
     */
    public List getCompletelyInstantiatedPropagators(String type) {
        return getPropagatorsWithPropertyOfType(Propagator::isCompletelyInstantiated, type);
    }

    /**
     * Return the list of reified propagators in the Model
     *
     * @return the list of reified propagators
     */
    public List getReifiedPropagators() {
        return retrievePropagatorWithProperty(Propagator::isReified);
    }

    /**
     * Return the list of reified propagators in the Model of the given type
     * Method retrievePropagatorData() must have been called prior to this for it to work.
     *
     * @return the list of reified propagators of the given type
     * @see ModelAnalyser#getConstraintTypes()
     */
    public List getReifiedPropagators(String type) {
        return getPropagatorsWithPropertyOfType(Propagator::isReified, type);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////     VariableTypeStatistics     //////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * The VariableTypeStatistics is the result of the analysis of variables of a given type and of a
     * given class. It gives several keys on the properties of the variables: whether they are instantiated,
     * constants, their domain's range, etc.
     *
     * @see org.chocosolver.solver.ModelAnalyser
     * @see org.chocosolver.solver.variables.Variable
     */
    public static class VariableTypeStatistics {
        /**
         * The type of the analysed variables
         * @see ModelAnalyser#VARS_TYPES
         */
        public final String varType;
        /**
         * The class' String of the analysed variables
         */
        public final String classVarType;
        public final int nbVariables;
        public final int nbInstantiatedVariables;
        /**
         * Map indicating the number of analysed variables of the type and class by domains' range
         */
        public final LinkedHashMap byDomainSize;
        /**
         * Map indicating the number of analysed variables of the type and class by number of propagators in which they are in the scope
         */
        public final Map byNbPropagators;
        /**
         * Map indicating the number of analysed variables of the type and class by the number of views pointing to them
         */
        public final Map byNbViews;

        private VariableTypeStatistics(String varType, String classVarType,
                                       int nbVariables, int nbInstantiatedVariables,
                                      LinkedHashMap byDomainSize, Map byNbPropagators,
                                      Map byNbViews) {
            this.varType = varType;
            this.classVarType = classVarType;
            this.nbVariables = nbVariables;
            this.nbInstantiatedVariables = nbInstantiatedVariables;
            this.byDomainSize = byDomainSize;
            this.byNbPropagators = byNbPropagators;
            this.byNbViews = byNbViews;
        }

        @Override
        public String toString() {
            return toString(false, true, false);
        }

        /**
         * Return a String describing the results of the analysis on the variables of given type and class.
         *
         * @param addInitialTab a boolean indicating whether to add a tabular at the beginning of lines (for pretty printing)
         * @param addVarType a boolean indicating whether to add the type of the analysed variables in the String
         * @param printAllStats a boolean indicating whether to add all the statistics to the String (if false, some criterion won't be added, for example nbInstantiatedVariables if equals to zero)
         * @return a String describing the VariableTypeStatistics
         * @see ModelAnalyser#printVariableAnalysis()
         */
        public String toString(boolean addInitialTab, boolean addVarType, boolean printAllStats) {
            StringBuilder sb = new StringBuilder(addInitialTab ? "\t" : "");
            if (addVarType) {
                sb.append(varType).append(".");
            }
            sb.append(classVarType).append(" = ").append(nbVariables).append("\n");
            if (printAllStats || nbInstantiatedVariables > 0) {
                sb.append(addInitialTab ? "\t" : "");
                sb.append("\t- Nb instantiated: ").append(nbInstantiatedVariables).append("\n");
            }
            if (printAllStats || byNbPropagators.containsKey(0)) {
                sb.append(addInitialTab ? "\t" : "");
                int nbUnconstrained = byNbPropagators.getOrDefault(0, 0);
                sb.append("\t- Nb unconstrained: ").append(nbUnconstrained).append("\n");
            }
            sb.append(addInitialTab ? "\t" : "");
            sb.append("\t- By domain size: ").append(prettyObjSizeMap(byDomainSize));
            if (printAllStats || byNbPropagators.keySet().size() != 1 || !byNbPropagators.containsKey(0)) {
                sb.append("\n");
                sb.append(addInitialTab ? "\t" : "");
                sb.append("\t- By number of propagators: ").append(prettyIntSizeMap(byNbPropagators));
            }
            if (printAllStats || byNbViews.keySet().size() != 1 || !byNbViews.containsKey(0)) {
                sb.append("\n");
                sb.append(addInitialTab ? "\t" : "");
                sb.append("\t- By number of views: ").append(prettyIntSizeMap(byNbViews));
            }
            return sb.toString();
        }

        private static VariableTypeStatistics createVariableTypeStatistics(Map>> mapTypeClassNbVars, String varType, String classNameOfType) {
            return VariableTypeStatistics.createVariableTypeStatistics(mapTypeClassNbVars, varType, classNameOfType, true);
        }

        private static VariableTypeStatistics createVariableTypeStatistics(Map>> mapTypeClassNbVars, String varType, String classNameOfType, boolean showByRange) {
            List list;
            if (mapTypeClassNbVars.containsKey(varType) && mapTypeClassNbVars.get(varType).containsKey(classNameOfType)) {
                list = mapTypeClassNbVars.get(varType).get(classNameOfType);
            } else {
                list = new ArrayList<>();
            }
            int nbVariables = list.size();
            int nbInstantiatedVariables = (int) list.stream().filter(Variable::isInstantiated).count();
            LinkedHashMap byDomainSize = new LinkedHashMap<>();
            Map> byDomainIntSize = Collections.unmodifiableMap(
                    new LinkedHashMap<>(list.stream().collect(Collectors.groupingBy(Variable::getDomainSize)))
            );
            if (showByRange) {
                int[] domainRangeLb = new int[]{1, 2, 3, 4, 10, 101, 1001};
                int[] domainRangeUb = new int[]{1, 2, 3, 9, 100, 1000, Integer.MAX_VALUE};
                for (int i = 0; i < domainRangeLb.length; i++) {
                    int rangeLb = domainRangeLb[i];
                    int rangeUb = domainRangeUb[i];
                    String domainRangeName;
                    if (rangeUb == Integer.MAX_VALUE) {
                        domainRangeName = ">" + rangeLb;
                    } else if (rangeLb == rangeUb) {
                        domainRangeName = Integer.toString(rangeLb);
                    } else {
                        domainRangeName = rangeLb + "-" + rangeUb;
                    }
                    List vars = byDomainIntSize.entrySet().stream()
                            .filter(entry -> rangeLb <= entry.getKey() && entry.getKey() <= rangeUb)
                            .map(Map.Entry::getValue)
                            .flatMap(List::stream)
                            .collect(Collectors.toList());
                    if (!vars.isEmpty()) {
                        byDomainSize.put(domainRangeName, vars.size());
                    }
                }
            } else {
                List domainSize = byDomainIntSize.keySet().stream().sorted().collect(Collectors.toList());
                for (Integer size : domainSize) {
                    if (!byDomainIntSize.get(size).isEmpty()) {
                        byDomainSize.put(size.toString(), byDomainIntSize.get(size).size());
                    }
                }
            }
            Map> byNbPropagatorsList = Collections.unmodifiableMap(
                    new LinkedHashMap<>(list.stream().collect(Collectors.groupingBy(Variable::getNbProps)))
            );
            Map byNbPropagators = new LinkedHashMap<>();
            List nbPropsList = byNbPropagatorsList.keySet().stream().sorted().collect(Collectors.toList());
            for (Integer nbProps : nbPropsList) {
                if (!byNbPropagatorsList.get(nbProps).isEmpty()) {
                    byNbPropagators.put(nbProps, byNbPropagatorsList.get(nbProps).size());
                }
            }
            Map> byNbViewsList = Collections.unmodifiableMap(
                    new LinkedHashMap<>(list.stream().collect(Collectors.groupingBy(Variable::getNbViews)))
            );
            Map byNbViews = new LinkedHashMap<>();
            List nbViewsList = byNbViewsList.keySet().stream().sorted().collect(Collectors.toList());
            for (Integer nbViews : nbViewsList) {
                if (!byNbViewsList.get(nbViews).isEmpty()) {
                    byNbViews.put(nbViews, byNbViewsList.get(nbViews).size());
                }
            }
            return new VariableTypeStatistics(
                    varType, classNameOfType,
                    nbVariables, nbInstantiatedVariables,
                    byDomainSize, byNbPropagators, byNbViews
            );
        }
    }

    /**
     * Generate a VariableTypeStatistics object for the given type and class of variables, based on the analysis.
     * Method retrieveVariableData() should have been called prior the call to this method for it to work correctly.
     *
     * @param varType the type of variables to analyse
     * @param classNameOfType the class of variables to analyse
     * @return the statistics on the type and class of variables
     * @see ModelAnalyser#retrieveModelData()
     * @see ModelAnalyser#analyseVariables()
     */
    private VariableTypeStatistics createVariableTypeStatistics(String varType, String classNameOfType) {
        return VariableTypeStatistics.createVariableTypeStatistics(mapTypeClassVars, varType, classNameOfType);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////     ConstraintTypeStatistics     /////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * The ConstraintTypeStatistics is the result of the analysis of propagators of a given constraint
     * type and of a given class. It gives several keys on the properties of the propagators: whether they are entailed,
     * passive, etc.
     *
     * @see org.chocosolver.solver.ModelAnalyser
     * @see org.chocosolver.solver.constraints.Constraint
     * @see org.chocosolver.solver.constraints.Propagator
     */
    public static class ConstraintTypeStatistics {
        /**
         * The constraint type of the analysed propagators
         * @see Constraint#getName()
         */
        public final String cstrType;
        /**
         * The class of the analysed propagators
         */
        public final String propType;
        public final int nbPropagators;
        public final int nbEntailedPropagators;
        public final int nbPassivePropagators;
        public final int nbCompletelyInstantiatedPropagators;
        public final int nbReifiedPropagators;
        /**
         * Map indicating the number of analysed propagators of the type of constraint and class by the number of variables in their scope
         */
        public final Map byNbVariables;
        /**
         * Map indicating the number of analysed propagators of the type of constraint and class by the number of uninstantiated variables in their scope
         */
        public final Map byArity;

        private ConstraintTypeStatistics(String cstrType, String propType,
                int nbPropagators, int nbEntailedPropagators, int nbPassivePropagators, int nbCompletelyInstantiatedPropagators,
                int nbReifiedPropagators, Map byNbVariables, Map byArity) {
            this.cstrType = cstrType;
            this.propType = propType;
            this.nbPropagators = nbPropagators;
            this.nbEntailedPropagators = nbEntailedPropagators;
            this.nbPassivePropagators = nbPassivePropagators;
            this.nbCompletelyInstantiatedPropagators = nbCompletelyInstantiatedPropagators;
            this.nbReifiedPropagators = nbReifiedPropagators;
            this.byNbVariables = byNbVariables;
            this.byArity = byArity;
        }

        @Override
        public String toString() {
            return toString(false, true, false);
        }

        /**
         * Return a String describing the results of the analysis on the propagators of given constraint type and propagator class.
         *
         * @param addInitialTab a boolean indicating whether to add a tabular at the beginning of lines (for pretty printing)
         * @param addCstrType a boolean indicating whether to add the type of constraint of the analysed propagators in the String
         * @param printAllStats a boolean indicating whether to add all the statistics to the String (if false, some criterion won't be added, for example nbEntailedPropagators if equals to zero)
         * @return a String describing the ConstraintTypeStatistics
         * @see ModelAnalyser#printConstraintAnalysis()
         */
        public String toString(boolean addInitialTab, boolean addCstrType, boolean printAllStats) {
            StringBuilder sb = new StringBuilder(addInitialTab ? "\t" : "");
            if (addCstrType) {
                sb.append(cstrType).append(".");
            }
            sb.append(propType).append(" = ").append(nbPropagators).append("\n");
            if (printAllStats || nbEntailedPropagators > 0) {
                sb.append(addInitialTab ? "\t" : "");
                sb.append("\t- Nb entailed: ").append(nbEntailedPropagators).append("\n");
            }
            if (printAllStats || nbPassivePropagators > 0) {
                sb.append(addInitialTab ? "\t" : "");
                sb.append("\t- Nb passive: ").append(nbPassivePropagators).append("\n");
            }
            if (printAllStats || nbCompletelyInstantiatedPropagators > 0) {
                sb.append(addInitialTab ? "\t" : "");
                sb.append("\t- Nb completely instantiated: ").append(nbCompletelyInstantiatedPropagators).append("\n");
            }
            if (printAllStats || nbReifiedPropagators > 0) {
                sb.append(addInitialTab ? "\t" : "");
                sb.append("\t- Nb reified: ").append(nbReifiedPropagators).append("\n");
            }
            sb.append(addInitialTab ? "\t" : "");
            sb.append("\t- By number of variables: ").append(prettyIntSizeMap(byNbVariables)).append("\n");
            sb.append(addInitialTab ? "\t" : "");
            sb.append("\t- By arity: ").append(prettyIntSizeMap(byArity));
            return sb.toString();
        }

        private static ConstraintTypeStatistics createConstraintTypeStatistics(Map>> mapTypeClassNbCstrs, String cstrType, String classNameOfType) {
            List list;
            if (mapTypeClassNbCstrs.containsKey(cstrType) && mapTypeClassNbCstrs.get(cstrType).containsKey(classNameOfType)) {
                list = mapTypeClassNbCstrs.get(cstrType).get(classNameOfType);
            } else {
                list = new ArrayList<>();
            }
            int nbPropagators = list.size();
            int nbEntailedPropagators = (int) list.stream().filter(p -> p.isEntailed().equals(ESat.TRUE)).count();
            int nbPassivePropagators = (int) list.stream().filter(Propagator::isPassive).count();
            int nbCompletelyInstantiatedPropagators = (int) list.stream().filter(Propagator::isCompletelyInstantiated).count();
            int nbReifiedPropagators = (int) list.stream().filter(Propagator::isReified).count();
            Map> byNbVarsList = list.stream().collect(Collectors.groupingBy(Propagator::arity));
            Map byNbVars = new LinkedHashMap<>();
            List nbVarsList = byNbVarsList.keySet().stream().sorted().collect(Collectors.toList());
            for (Integer nbVars : nbVarsList) {
                if (!byNbVarsList.get(nbVars).isEmpty()) {
                    byNbVars.put(nbVars, byNbVarsList.get(nbVars).size());
                }
            }
            Map> byArityList = list.stream().collect(Collectors.groupingBy(Propagator::arity));
            Map byArity = new LinkedHashMap<>();
            List arityList = byArityList.keySet().stream().sorted().collect(Collectors.toList());
            for (Integer arity : arityList) {
                if (!byArityList.get(arity).isEmpty()) {
                    byArity.put(arity, byArityList.get(arity).size());
                }
            }
            return new ConstraintTypeStatistics(
                    cstrType, classNameOfType,
                    nbPropagators, nbEntailedPropagators, nbPassivePropagators, nbCompletelyInstantiatedPropagators,
                    nbReifiedPropagators, byNbVars, byArity
            );
        }
    }

    /**
     * Generate a ConstraintTypeStatistics object for the given constraint type and class of propagators, based on the analysis.
     * Method retrievePropagatorData() should have been called prior the call to this method for it to work correctly.
     *
     * @param cstrType the type of constraint of the propagators to analyse
     * @param classNameOfType the class of propagators to analyse
     * @return the statistics on the type of constraint and class of propagators
     * @see ModelAnalyser#retrieveModelData()
     * @see ModelAnalyser#analyseConstraints()
     */
    private ConstraintTypeStatistics createConstraintTypeStatistics(String cstrType, String classNameOfType) {
        return ConstraintTypeStatistics.createConstraintTypeStatistics(mapTypeClassCstrs, cstrType, classNameOfType);
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    //////////////////////////////////////////////     ModelAnalysis     ///////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    /**
     * The ModelAnalysis is the result of the analysis of the Model. Analysis are given
     * through VariableTypeStatistics and ConstraintTypeStatistics objects.
     *
     * @see org.chocosolver.solver.ModelAnalyser
     * @see org.chocosolver.solver.ModelAnalyser.VariableTypeStatistics
     * @see org.chocosolver.solver.ModelAnalyser.ConstraintTypeStatistics
     */
    public static class ModelAnalysis {
        public final VariableTypeStatistics[] varsTypeStats;
        public final ConstraintTypeStatistics[] cstrsTypeStats;

        public ModelAnalysis(VariableTypeStatistics[] varsTypeStats, ConstraintTypeStatistics[] cstrsTypeStats) {
            this.varsTypeStats = varsTypeStats;
            this.cstrsTypeStats = cstrsTypeStats;
        }
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    /////////////////////////////////////////////////     PRINTING     /////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private static  String prettyIntSizeMap(Map map) {
        StringBuilder sb = new StringBuilder("{");
        List sortedArities = map.keySet().stream().sorted().collect(Collectors.toList());
        for (int i = 0; i < sortedArities.size(); i++) {
            sb.append(sortedArities.get(i)).append(": ").append(map.get(sortedArities.get(i)));
            if (i + 1 < sortedArities.size()) {
                sb.append(", ");
            }
        }
        sb.append("}");
        return sb.toString();
    }

    private static  String prettyObjSizeMap(LinkedHashMap map) {
        StringBuilder sb = new StringBuilder("{");
        ArrayList sortedArities = new ArrayList<>(map.keySet());
        for (int i = 0; i < sortedArities.size(); i++) {
            sb.append(sortedArities.get(i)).append(": ").append(map.get(sortedArities.get(i)));
            if (i + 1 < sortedArities.size()) {
                sb.append(", ");
            }
        }
        sb.append("}");
        return sb.toString();
    }

    private void printVariableAnalysis(boolean retrieveData) {
        if (retrieveData) {
            retrieveVariableData();
        }
        Logger logger = model.getSolver().log();
        logger.green().println("BEGINNING OF VARIABLES ANALYSIS");
        for (String c : getVariableTypes()) {
            logger.blue().println(c);
            List list = getVariableClassNamesOfType(c);
            for (int i = 0; i < list.size(); i++) {
                String varType = list.get(i);
                VariableTypeStatistics varTypeStats = createVariableTypeStatistics(c, varType);
                logger.println(varTypeStats.toString(true, false, true));
                logger.println("");
            }
        }
        logger.red().println("END OF VARIABLES ANALYSIS");
    }

    /**
     * Print the analysis on variables in all Solver's PrintStream.
     *
     * @see ModelAnalyser#printAnalysis()
     */
    public void printVariableAnalysis() {
        printVariableAnalysis(true);
    }

    private void printConstraintAnalysis(boolean retrieveData) {
        if (retrieveData) {
            retrievePropagatorData();
        }
        Logger logger = model.getSolver().log();
        logger.green().println("BEGINNING OF CONSTRAINTS ANALYSIS");
        for (String cstrType : getConstraintTypes()) {
            logger.blue().println(cstrType);
            List list = getConstraintClassNamesOfType(cstrType);
            for (int i = 0; i < list.size(); i++) {
                String propType = list.get(i);
                ConstraintTypeStatistics cstrTypeStats = createConstraintTypeStatistics(cstrType, propType);
                logger.println(cstrTypeStats.toString(true, false, true));
                logger.println("");
            }
        }
        logger.red().println("END OF CONSTRAINTS ANALYSIS");
    }

    /**
     * Print the analysis on propagators in all Solver's PrintStream.
     *
     * @see ModelAnalyser#printAnalysis()
     */
    public void printConstraintAnalysis() {
        printConstraintAnalysis(true);
    }

    /**
     * Print the analysis of the Model in all Solver's PrintStream.
     */
    public void printAnalysis() {
        retrieveModelData();
        Logger logger = model.getSolver().log();
        logger.println("");
        logger.green().println("BEGINNING OF MODEL ANALYSIS");
        logger.println("");
        printVariableAnalysis(false);
        logger.println("");
        printConstraintAnalysis(false);
        logger.println("");
        logger.red().println("END OF MODEL ANALYSIS");
        logger.println("");
    }

    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
    ////////////////////////////////////////////////     CSP GRAPH     /////////////////////////////////////////////////
    ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

    private static class Node {
        private static int ID = 0;
        public final int id;
        public final T obj;
        public final Map,Integer> neighbors = new HashMap<>();
        public Node(T t) {
            this.id = ID++;
            this.obj = t;
        }
    }

    private static class Graph {
        public final List> nodes = new ArrayList<>();
        public final Map> mapObjToNodes = new HashMap<>();

        private static final String NODE = "\t%d [label = \"%s\" shape = circle];\n";
        private static final String EDGE = "\t%d -- %d [weight = %d];\n";

        private String buildGvString() {
            StringBuilder sb = new StringBuilder("graph G{\n");
            sb.append("\trankdir=TB;\n\n");
            for (Node node : nodes) {
                if (node.obj instanceof Variable) {
                    Variable var = (Variable) node.obj;
                    sb.append(String.format(NODE, node.id, var.getName()));
                } else {
                    Propagator prop = (Propagator) node.obj;
                    sb.append(String.format(NODE, node.id, getClassName(prop.getClass()) + "-" + prop.getId()));
                }
            }
            sb.append("\n\n");
            Set edgesSet = new HashSet<>();
            for (int i = 0; i < nodes.size(); i++) {
                Node nodei = nodes.get(i);
                for (Map.Entry,Integer> entry : nodei.neighbors.entrySet()) {
                    Node nodej = entry.getKey();
                    Integer pair1 = nodei.id * Node.ID + nodej.id;
                    Integer pair2 = nodej.id * Node.ID + nodei.id;
                    if (!edgesSet.contains(pair1) && !edgesSet.contains(pair2)) {
                        edgesSet.add(pair1);
                        sb.append(String.format(EDGE, nodei.id, nodej.id, entry.getValue()));
                    }
                }
            }
            sb.append("}");
            return sb.toString();
        }
    }

    private Graph buildPropagatorOrientedGraph(boolean withWeight) {
        Node.ID = 0;
        Propagator[] propagators = Arrays.stream(this.model.getCstrs())
                .map(Constraint::getPropagators)
                .flatMap(Arrays::stream)
                .distinct()
                .toArray(Propagator[]::new);
        Graph graph = new Graph<>();
        // Add nodes
        for (Propagator prop : propagators) {
            Node node = new Node<>(prop);
            graph.nodes.add(node);
            graph.mapObjToNodes.put(prop, node);
        }
        // Add edges
        for (int i = 0; i < propagators.length; i++) {
            final int finali = i;
            for (int j = i + 1; j < propagators.length; j++) {
                int nbCommonVars = 0;
                for (int k = 0; k < propagators[i].getNbVars(); k++) {
                    final int finalk = k;
                    boolean commonVar = Arrays.stream(propagators[j].getVars())
                            .filter(Objects::nonNull)
                            .anyMatch(v -> v.equals(propagators[finali].getVar(finalk)));
                    if (commonVar) {
                        nbCommonVars++;
                    }
                }
                if (nbCommonVars > 0) {
                    Node nodei = graph.mapObjToNodes.get(propagators[i]);
                    Node nodej = graph.mapObjToNodes.get(propagators[j]);
                    nodei.neighbors.put(nodej, withWeight ? nbCommonVars : 1);
                    nodej.neighbors.put(nodei, withWeight ? nbCommonVars : 1);
                }
            }
        }
        return graph;
    }

    private  void writeGraph(Graph graph, String orientation, String path) {
        Path instance = Paths.get(path);
        if (Files.exists(instance)) {
            try {
                Files.delete(instance);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            Files.createFile(instance);
            Files.write(instance, graph.buildGvString().getBytes(), APPEND);
        } catch (IOException e) {
            System.err.println("Could not write " + orientation + "-oriented graph");
        }
    }

    /**
     * Write a gv file, at the given path, corresponding to the propagator-oriented graph of the Model
     *
     * @param path the path to which write the gv file
     * @see ModelAnalyser#writePropagatorOrientedGraph(String, boolean)
     */
    public void writePropagatorOrientedGraph(String path) {
        writePropagatorOrientedGraph(path, true);
    }

    /**
     * Write a gv file, at the given path, corresponding to the propagator-oriented graph of the Model
     *
     * @param path the path to which write the gv file
     * @param withWeight whether to take the weight into account
     */
    public void writePropagatorOrientedGraph(String path, boolean withWeight) {
        Graph graph = buildPropagatorOrientedGraph(withWeight);
        writeGraph(graph, "propagator", path);
    }

    private Graph buildVariableOrientedGraph(boolean withWeight) {
        Node.ID = 0;
        Variable[] variables = Arrays.stream(this.model.getVars())
                .distinct()
                .toArray(Variable[]::new);
        Graph graph = new Graph<>();
        // Add nodes
        for (Variable var : variables) {
            Node node = new Node<>(var);
            graph.nodes.add(node);
            graph.mapObjToNodes.put(var, node);
        }
        Map> mapVarProps = new HashMap<>();
        Propagator[] propagators = Arrays.stream(this.model.getCstrs())
                .map(Constraint::getPropagators)
                .flatMap(Arrays::stream)
                .distinct()
                .toArray(Propagator[]::new);
        for (Propagator prop : propagators) {
            for (Variable var : prop.getVars()) {
                if (!mapVarProps.containsKey(var)) {
                    mapVarProps.put(var, new HashSet<>());
                }
                mapVarProps.get(var).add(prop);
            }
        }
        // Add edges
        for (int i = 0; i < variables.length; i++) {
            if (!mapVarProps.containsKey(variables[i])) {
                continue;
            }
            for (int j = i + 1; j < variables.length; j++) {
                if (!mapVarProps.containsKey(variables[j])) {
                    continue;
                }
                int nbCommonPropagators = (int) mapVarProps.get(variables[i]).stream()
                        .filter(Objects::nonNull)
                        .filter(mapVarProps.get(variables[j])::contains)
                        .count();
                if (nbCommonPropagators > 0) {
                    Node nodei = graph.mapObjToNodes.get(variables[i]);
                    Node nodej = graph.mapObjToNodes.get(variables[j]);
                    nodei.neighbors.put(nodej, withWeight ? nbCommonPropagators : 1);
                    nodej.neighbors.put(nodei, withWeight ? nbCommonPropagators : 1);
                }
            }
        }
        return graph;
    }

    /**
     * Write a gv file, at the given path, corresponding to the variable-oriented graph of the Model
     *
     * @param path the path to which write the gv file
     * @see ModelAnalyser#writeVariableOrientedGraph(String, boolean)
     */
    public void writeVariableOrientedGraph(String path) {
        writeVariableOrientedGraph(path, true);
    }

    /**
     * Write a gv file, at the given path, corresponding to the variable-oriented graph of the Model
     *
     * @param path the path to which write the gv file
     * @param withWeight whether to take the weight into account
     */
    public void writeVariableOrientedGraph(String path, boolean withWeight) {
        Graph graph = buildVariableOrientedGraph(withWeight);
        writeGraph(graph, "variable", path);
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy