001package org.dllearner.algorithms.pattern;
002
003import org.dllearner.core.owl.OWLObjectIntersectionOfImplExt;
004import org.dllearner.core.owl.OWLObjectUnionOfImplExt;
005import org.semanticweb.owlapi.model.*;
006import org.semanticweb.owlapi.util.DefaultPrefixManager;
007import org.semanticweb.owlapi.util.OWLClassExpressionVisitorExAdapter;
008import uk.ac.manchester.cs.owl.owlapi.OWLClassImpl;
009import uk.ac.manchester.cs.owl.owlapi.OWLDataFactoryImpl;
010
011import javax.annotation.Nonnull;
012import java.lang.reflect.Constructor;
013import java.lang.reflect.InvocationTargetException;
014import java.util.*;
015import java.util.concurrent.atomic.AtomicInteger;
016import java.util.function.BinaryOperator;
017import java.util.function.Function;
018import java.util.function.Supplier;
019import java.util.stream.Collectors;
020import java.util.stream.IntStream;
021import java.util.stream.LongStream;
022import java.util.stream.Stream;
023
024/**
025 * @author Lorenz Buehmann
026 */
027public class OWLClassExpressionGeneralizer extends OWLClassExpressionVisitorExAdapter<Stream<OWLClassExpression>> {
028
029    private AtomicInteger cnt = new AtomicInteger(1);
030
031    private OWLDataFactory df;
032    private String NS = "http://dl-learner.org/pattern/";
033    private PrefixManager pm = new DefaultPrefixManager();
034
035    private final OWLClass CE = new OWLClassImpl(IRI.create(NS + "CE"));
036    private final OWLClass INTERSECTION = new OWLClassImpl(IRI.create(NS + "INTERSECTION"));
037    private final OWLClass UNION = new OWLClassImpl(IRI.create(NS + "UNION"));
038
039    public OWLClassExpressionGeneralizer(OWLDataFactory df) {
040        super(Stream.empty());
041        this.df = df;
042
043        pm.setDefaultPrefix(NS);
044    }
045
046    @Nonnull
047    @Override
048    protected Stream<OWLClassExpression> doDefault(@Nonnull OWLClassExpression ce) {
049        return Stream.of(ce);
050    }
051
052    public OWLClassExpressionGeneralizer() {
053        this(new OWLDataFactoryImpl());
054    }
055
056    Function<OWLEntity, OWLEntity> classRenamingFn = k -> k;
057
058    public Set<OWLClassExpression> generalize(OWLClassExpression ce) {
059        cnt.set(1);
060
061        Stream<OWLClassExpression> genCEs = ce.accept(this);
062
063//        OWLClassExpressionRenamer renamer = new OWLClassExpressionRenamer(df, new HashMap<>());
064//        renamer.setClassRenamingFn(classRenamingFn);
065
066        return genCEs.map(genCE -> {
067            OWLClassExpressionRenamer renamer = new OWLClassExpressionRenamer(df, new HashMap<>());
068            renamer.setClassRenamingFn(classRenamingFn);
069            renamer.reset();
070            return renamer.rename(genCE);
071        }).collect(Collectors.toCollection(TreeSet::new));
072    }
073
074    @Override
075    public Stream<OWLClassExpression> visit(OWLClass ce) {
076        return Stream.of(newCE());
077    }
078
079    @Override
080    public Stream<OWLClassExpression> visit(OWLObjectSomeValuesFrom ce) {
081        Stream<OWLClassExpression> res = ce.getFiller().accept(this)
082                .map(f -> df.getOWLObjectSomeValuesFrom(ce.getProperty(), f));
083//        res.add(newCE());
084        return res;
085    }
086
087    @Override
088    public Stream<OWLClassExpression> visit(OWLDataSomeValuesFrom ce) {
089        return Stream.of(ce);
090    }
091
092    @Override
093    public Stream<OWLClassExpression> visit(OWLObjectAllValuesFrom ce) {
094        Stream<OWLClassExpression> res = ce.getFiller().accept(this)
095                .map(f -> df.getOWLObjectAllValuesFrom(ce.getProperty(), f));
096//        res.add(newCE());
097        return res;
098    }
099
100    @Override
101    public Stream<OWLClassExpression> visit(OWLDataAllValuesFrom ce) {
102        return Stream.of(ce);
103    }
104
105    @Override
106    public Stream<OWLClassExpression> visit(OWLObjectMinCardinality ce) {
107        return (ce.getCardinality() == 1)
108                ? df.getOWLObjectSomeValuesFrom(ce.getProperty(), ce.getFiller()).accept(this)
109                : ce.getFiller().accept(this)
110                    .map(f -> df.getOWLObjectMinCardinality(ce.getCardinality(), ce.getProperty(), f));
111    }
112
113    @Override
114    public Stream<OWLClassExpression> visit(OWLObjectMaxCardinality ce) {
115        return ce.getFiller().accept(this)
116                .map(f -> df.getOWLObjectMaxCardinality(ce.getCardinality(), ce.getProperty(), f));
117    }
118
119    @Override
120    public Stream<OWLClassExpression> visit(OWLObjectExactCardinality ce) {
121        return ce.getFiller().accept(this)
122                .map(f -> df.getOWLObjectExactCardinality(ce.getCardinality(), ce.getProperty(), f));
123    }
124
125    @Override
126    public Stream<OWLClassExpression> visit(OWLDataMinCardinality ce) {
127        return Stream.of(ce);
128    }
129
130    @Override
131    public Stream<OWLClassExpression> visit(OWLDataMaxCardinality ce) {
132        return Stream.of(ce);
133    }
134
135    @Override
136    public Stream<OWLClassExpression> visit(OWLDataExactCardinality ce) {
137        return Stream.of(ce);
138    }
139
140    @Override
141    public Stream<OWLClassExpression> visit(OWLObjectIntersectionOf ce) {
142        return Stream.concat(processNaryBooleanClassExpression(ce), Stream.of(newAnd()));
143    }
144
145    private OWLClass newAnd() {
146        return df.getOWLClass("AND", pm);
147    }
148
149    @Override
150    public Stream<OWLClassExpression> visit(OWLObjectUnionOf ce) {
151        return Stream.concat(processNaryBooleanClassExpression(ce), Stream.of(UNION));
152    }
153
154    private Stream<OWLClassExpression> processNaryBooleanClassExpression(OWLNaryBooleanClassExpression ce) {
155        try {
156            List<OWLClassExpression> operands = ce.getOperandsAsList();
157
158            // split by atomic class and complex CE
159            Map<Boolean, List<OWLClassExpression>> operandsSplit = operands.stream().collect(Collectors.partitioningBy(OWLClassExpression::isAnonymous));
160
161            List<OWLClassExpression> operandsAtomic = operandsSplit.get(false);
162            List<OWLClassExpression> operandsComplex = operandsSplit.get(true);
163
164            // compute the generalizations for the complex CEs
165            Map<OWLClassExpression, List<OWLClassExpression>> operandToGeneralizations = operandsComplex.stream().collect(
166                    Collectors.toMap(
167                            Function.identity(),
168                            op -> op.accept(OWLClassExpressionGeneralizer.this).collect(Collectors.toList())));
169
170            if(!operandsAtomic.isEmpty()) {
171                operandsComplex.add(operandsAtomic.get(0));
172                operandToGeneralizations.put(operandsAtomic.get(0), Collections.singletonList(CE));
173            }
174
175            Stream<List<OWLClassExpression>> combinations = getCombinationsStream(operandsComplex);
176
177            final Constructor<? extends OWLNaryBooleanClassExpression> constructor = ce.getClass().getConstructor(Set.class);
178
179            return combinations.flatMap(c -> {
180                List<List<OWLClassExpression>> opsGen = c.stream().map(op -> operandToGeneralizations.get(op)).collect(Collectors.toList());
181
182                Set<List<OWLClassExpression>> operandCombinations = getCombinations(opsGen);
183
184                return operandCombinations.stream().map(opC -> {
185                    // add placeholder CE for missing operands
186                    if (opC.size() < operands.size()) {
187                        opC.add(newCE());
188                    }
189
190                    Set<OWLClassExpression> newOperands = new HashSet<>(opC);
191
192                    try {
193                        return (newOperands.size() == 1) ? newOperands.iterator().next() :
194                                ((ce.getClassExpressionType() == ClassExpressionType.OBJECT_INTERSECTION_OF)
195                                        ? new OWLObjectIntersectionOfImplExt(opC)
196                                        : new OWLObjectUnionOfImplExt(opC));
197//                                (OWLClassExpression)constructor.newInstance(newOperands);
198                    } catch (Exception e) {
199                        e.printStackTrace();
200                    }
201                    return null;
202                });
203
204            });
205        } catch (NoSuchMethodException e) {
206            e.printStackTrace();
207        }
208        throw new RuntimeException("Failed to process boolean CE");
209    }
210
211    private OWLClassExpression newCE() {
212//        return df.getOWLClass("CE" + cnt.getAndIncrement(), pm);
213        return CE;
214    }
215
216    public static <T> Stream<List<T>> getCombinationsStream(List<T> list) {
217        // there are 2 ^ list.size() possible combinations
218        // stream through them and map the number of the combination to the combination
219        return LongStream.range(1 , 1 << list.size())
220                .mapToObj(l -> bitMapToList(l, list));
221    }
222
223    public static <T> List<T> bitMapToList(long bitmap, List<T> list) {
224        // use the number of the combination (bitmap) as a bitmap to filter the input list
225        return IntStream.range(0, list.size())
226                .filter(i -> 0 != ((1 << i) & bitmap))
227                .mapToObj(list::get)
228                .collect(Collectors.toList());
229    }
230
231    public static <T> Set<List<T>> getCombinations(List<List<T>> lists) {
232        Set<List<T>> combinations = new HashSet<List<T>>();
233        Set<List<T>> newCombinations;
234
235        int index = 0;
236
237        // extract each of the integers in the first list
238        // and add each to ints as a new list
239        for(T i: lists.get(0)) {
240            List<T> newList = new ArrayList<T>();
241            newList.add(i);
242            combinations.add(newList);
243        }
244        index++;
245        while(index < lists.size()) {
246            List<T> nextList = lists.get(index);
247            newCombinations = new HashSet<List<T>>();
248            for(List<T> first: combinations) {
249                for(T second: nextList) {
250                    List<T> newList = new ArrayList<T>();
251                    newList.addAll(first);
252                    newList.add(second);
253                    newCombinations.add(newList);
254                }
255            }
256            combinations = newCombinations;
257
258            index++;
259        }
260
261        return combinations;
262    }
263
264    private static <T> Stream<T> cartesian(BinaryOperator<T> aggregator,
265                                           Supplier<Stream<T>>... streams) {
266        return Arrays.stream(streams)
267                .reduce((s1, s2) ->
268                        () -> s1.get().flatMap(t1 -> s2.get().map(t2 -> aggregator.apply(t1, t2))))
269                .orElse(Stream::empty).get();
270    }
271}