From 1547c2f293cb4220539e92ebdf3cfc3159624be8 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 1 Oct 2025 12:50:22 +0200 Subject: [PATCH 001/257] sample file for folder structure --- src/main/java/de/featjar/feature/model/analysis/info.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/info.md diff --git a/src/main/java/de/featjar/feature/model/analysis/info.md b/src/main/java/de/featjar/feature/model/analysis/info.md new file mode 100644 index 00000000..16dc75b2 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/info.md @@ -0,0 +1 @@ +example file. Put your classes here. \ No newline at end of file From 2b5111b31435c93ecbaa0416cf82ef79d78fe434 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 1 Oct 2025 15:22:24 +0200 Subject: [PATCH 002/257] feat basic start for Simple Tree Properties --- .../model/analysis/SimpleTreeProperties.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java new file mode 100644 index 00000000..5026ac0f --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -0,0 +1,57 @@ +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.base.tree.Trees; +import de.featjar.base.tree.visitor.TreeDepthCounter; +import de.featjar.feature.model.*; + +public class SimpleTreeProperties { + + // todo + public int topFeatures(IFeatureTree tree) { + + return tree.getRoot().getChildrenCount(); // do groups count as features? + } + + public int leafFeatures(IFeatureTree tree) { + return 0; + } + + public int treeDepth(IFeatureTree tree) { + TreeDepthCounter visitor = new TreeDepthCounter(); + Result result = Trees.traverse(tree.getRoot(),visitor); + return result.get(); + } + + public void testMethode() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + //rootTree.mutate().toAndGroup(); + IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); + IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); + + IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); + IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); + + /* + TreePrinter visitor = new TreePrinter(); + Result traverseResult = Trees.traverse(rootTree, visitor); + System.out.println(traverseResult.get()); + */ + + int depth = treeDepth(rootTree); + System.out.println(depth); + + + } + + public static void main(String[] args){ + + SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); + simpleTreeProperties.testMethode(); + + } + +} From f61005179026f2fd780fc97c0fb535306cf31a69 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 1 Oct 2025 15:23:55 +0200 Subject: [PATCH 003/257] feat basic start for Simple Tree Properties --- .../de/featjar/feature/model/analysis/SimpleTreeProperties.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index 5026ac0f..022edc72 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -8,7 +8,6 @@ public class SimpleTreeProperties { - // todo public int topFeatures(IFeatureTree tree) { return tree.getRoot().getChildrenCount(); // do groups count as features? From 39800b8ee6ee3e178e9cdb73ac4e74eb90c9db74 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Wed, 1 Oct 2025 16:20:28 +0200 Subject: [PATCH 004/257] feat: added statistics output functionality WIP --- .../feature/model/cli/PrintStatistics.java | 117 ++++++++++++++++++ src/main/resources/extensions.xml | 3 + 2 files changed, 120 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/cli/PrintStatistics.java diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java new file mode 100644 index 00000000..e64ba91d --- /dev/null +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -0,0 +1,117 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-formula. + * + * formula is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * formula is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with formula. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.cli; + +import de.featjar.base.FeatJAR; +import de.featjar.base.cli.ACommand; +import de.featjar.base.cli.Option; +import de.featjar.base.cli.OptionList; +import de.featjar.base.data.Result; +import de.featjar.base.io.IO; +import de.featjar.base.io.csv.CSVFile; +import de.featjar.base.tree.Trees; +import de.featjar.formula.cli.PrintCommand.WhitespaceString; +import de.featjar.formula.io.FormulaFormats; +import de.featjar.formula.io.textual.ExpressionSerializer; +import de.featjar.formula.io.textual.Symbols; +import de.featjar.formula.structure.IFormula; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; +import java.util.Optional; +import java.util.HashMap; + + +/** + * Prints statistics about a provided Feature Model. + * + * @author Knut & Kilian + */ +public class PrintStatistics extends ACommand { + + public static final Option PRETTY_PRINT = Option.newFlag("print") + .setDescription("Pretty prints the numbers"); + + public static final Option INPUT_OPTION = Option.newOption("path", Option.PathParser) + .setDescription("Path to input file(s)"); + // .setValidator(Option.PathValidator) + + private HashMap data = new HashMap(); + + @Override + public int run(OptionList optionParser) { + Boolean bool_prettyPrint = optionParser.get(PRETTY_PRINT); + Path path = optionParser.get(INPUT_OPTION); + + + if (!path.equals(null)) { + FeatJAR.log().message(path); + } + if(bool_prettyPrint) { + FeatJAR.log().message("PRETTY"); + } + + FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + messageLog()); + + return 0; + } + + private StringBuilder messageLog() { + + data.put("numOfTopFeatures", 3); + data.put("numOfLeafFeatures", 12); + data.put("treeDepth", 3); + data.put("avgNumOfChildren", 3); + data.put("numInOrGroups", 7); + data.put("numInAltGroups", 5); + data.put("numOfAtoms", 8); + data.put("avgNumOfAtomsPerConstraints", 4); + + StringBuilder outputString = new StringBuilder(); + + for (Map.Entry entry : data.entrySet()) { + outputString.append(String.format("%-30s : %s%n", entry.getKey(), entry.getValue())); + } + return outputString; + } + + private String writeTocsv() { + //Path path = new Path() + //CSVFile file = new CSVFile(path); + return ""; + } + + + @Override + public Optional getDescription() { + return Optional.of("Prints out statistics about a given Feature Model."); + } + + @Override + public Optional getShortName() { + return Optional.of("printStats"); + } +} diff --git a/src/main/resources/extensions.xml b/src/main/resources/extensions.xml index 4f895725..ae7ffdfe 100644 --- a/src/main/resources/extensions.xml +++ b/src/main/resources/extensions.xml @@ -4,4 +4,7 @@ + + + From e27f02e9a31defcb31dba98287a825d3f3f062d7 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 1 Oct 2025 16:39:42 +0200 Subject: [PATCH 005/257] feat visitor implementation of leaf counter --- .../model/analysis/SimpleTreeProperties.java | 35 ++++++++-- .../model/analysis/TreeLeafCounter.java | 67 +++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index 022edc72..6fe4ade2 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -6,6 +6,8 @@ import de.featjar.base.tree.visitor.TreeDepthCounter; import de.featjar.feature.model.*; +import java.util.List; + public class SimpleTreeProperties { public int topFeatures(IFeatureTree tree) { @@ -13,10 +15,34 @@ public int topFeatures(IFeatureTree tree) { return tree.getRoot().getChildrenCount(); // do groups count as features? } - public int leafFeatures(IFeatureTree tree) { - return 0; + /* + + public int leafFeaturesRecursive(IFeatureTree currentNode) { + + List children = currentNode.getChildren(); + if (children.isEmpty()) { + return 1; + } + + int result = 0; + for (IFeatureTree child : children) { + result += leafFeaturesRecursive(child); + } + return result; + } + + public int leafFeaturesStarter(IFeatureTree tree) { + return this.leafFeaturesRecursive(tree.getRoot()); } + */ + + public int leafFeaturesCounter(IFeatureTree tree) { + Result traverseResult = Trees.traverse(tree, new TreeLeafCounter()); + return traverseResult.get(); + } + + public int treeDepth(IFeatureTree tree) { TreeDepthCounter visitor = new TreeDepthCounter(); Result result = Trees.traverse(tree.getRoot(),visitor); @@ -40,8 +66,9 @@ public void testMethode() { System.out.println(traverseResult.get()); */ - int depth = treeDepth(rootTree); - System.out.println(depth); + // int depth = treeDepth(rootTree); + int leafCount = leafFeaturesCounter(rootTree); + System.out.println(leafCount); } diff --git a/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java new file mode 100644 index 00000000..3ba95539 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ + +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; + +import java.util.List; + +/** + * Counts the number of nodes that have no child nodes + * Can be passed a class up to which should be counted (e.g., to exclude details in a tree). + * + * @author Sebastian Krieter + */ +public class TreeLeafCounter implements ITreeVisitor, Integer> { + private Class> terminalClass = null; + private int leafCount = 0; + + public Class> getTerminalClass() { + return terminalClass; + } + + public void setTerminalClass(Class> terminalClass) { + this.terminalClass = terminalClass; + } + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + if (!node.hasChildren()) { + leafCount++; + } + return TraversalAction.CONTINUE; + + } + + @Override + public void reset() { + leafCount = 0; + } + + @Override + public Result getResult() { + return Result.of(leafCount); + } +} From 36242f4038bb4931d00f14f2d26b74d8edb8201d Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 1 Oct 2025 16:50:10 +0200 Subject: [PATCH 006/257] feat: added visitor to count atoms and its computation method --- .../model/analysis/ConstraintProperties.java | 50 +++++++++++++++++++ .../ConstraintPropertiesFeatureShare.java | 45 +++++++++++++++++ .../ComputeConstraintProperties.java | 42 ++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java create mode 100644 src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java b/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java new file mode 100644 index 00000000..0a3877b3 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.formula.structure.ATerminalExpression; +import java.util.List; + +public class ConstraintProperties implements ITreeVisitor, Integer> { + private int atomsCount = 0; + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + if (node instanceof ATerminalExpression) { + atomsCount++; + } + return TraversalAction.CONTINUE; + } + + @Override + public Result getResult() { + return Result.of(atomsCount); + } + + @Override + public void reset() { + atomsCount = 0; + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java b/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java new file mode 100644 index 00000000..10e3502a --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; +import java.util.List; + +public class ConstraintPropertiesFeatureShare implements ITreeVisitor, Integer> { + + @Override + public TraversalAction firstVisit(List> path) { + + return TraversalAction.CONTINUE; + } + + @Override + public Result getResult() { + return Result.of(0); + } + + @Override + public void reset() { + ; + } +} diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java b/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java new file mode 100644 index 00000000..c1be590a --- /dev/null +++ b/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import de.featjar.base.computation.*; +import de.featjar.base.computation.AComputation; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.analysis.ConstraintProperties; +import de.featjar.formula.structure.IFormula; +import java.util.List; + +public class ComputeConstraintProperties extends AComputation { + protected static final Dependency tree = Dependency.newDependency(IFormula.class); + + public ComputeConstraintProperties(IComputation iformula) { + super(iformula); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + return Trees.traverse(tree.get(dependencyList), new ConstraintProperties()); + } +} From 8a67834be471d8189a7c160bf0c20a6785445c33 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 1 Oct 2025 16:51:05 +0200 Subject: [PATCH 007/257] chores: removed placeholder file --- src/main/java/de/featjar/feature/model/analysis/info.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/main/java/de/featjar/feature/model/analysis/info.md diff --git a/src/main/java/de/featjar/feature/model/analysis/info.md b/src/main/java/de/featjar/feature/model/analysis/info.md deleted file mode 100644 index 16dc75b2..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/info.md +++ /dev/null @@ -1 +0,0 @@ -example file. Put your classes here. \ No newline at end of file From 4939f21aea3f30822491df377251d2af8ae7b169 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 2 Oct 2025 10:45:29 +0200 Subject: [PATCH 008/257] feat: Changed from IFormula input to FeatureModel input in analysis and compuational classes feat: added options for counting different type of atoms test: Added a test for the computational class --- .../model/analysis/ConstraintProperties.java | 14 ++++- .../ComputeConstraintProperties.java | 29 +++++++--- .../computation/ConstraintPropertiesTest.java | 53 +++++++++++++++++++ 3 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java b/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java index 0a3877b3..55d431b1 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java @@ -23,16 +23,26 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; -import de.featjar.formula.structure.ATerminalExpression; +import de.featjar.formula.structure.term.value.Constant; +import de.featjar.formula.structure.term.value.Variable; import java.util.List; public class ConstraintProperties implements ITreeVisitor, Integer> { private int atomsCount = 0; + private boolean countVariables = true; + private boolean countConstants = true; + + public ConstraintProperties(boolean countVariables, boolean countConstants) { + this.countConstants = countConstants; + this.countVariables = countVariables; + } @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); - if (node instanceof ATerminalExpression) { + if (countConstants && node instanceof Constant) { + atomsCount++; + } else if (countVariables && node instanceof Variable) { atomsCount++; } return TraversalAction.CONTINUE; diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java b/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java index c1be590a..d49f8b67 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java @@ -21,22 +21,39 @@ package de.featjar.feature.model.computation; import de.featjar.base.computation.*; -import de.featjar.base.computation.AComputation; import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IConstraint; import de.featjar.feature.model.analysis.ConstraintProperties; -import de.featjar.formula.structure.IFormula; +import java.util.Collection; +import java.util.Iterator; import java.util.List; public class ComputeConstraintProperties extends AComputation { - protected static final Dependency tree = Dependency.newDependency(IFormula.class); + protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); - public ComputeConstraintProperties(IComputation iformula) { - super(iformula); + public ComputeConstraintProperties(IComputation featureModel) { + super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); } @Override public Result compute(List dependencyList, Progress progress) { - return Trees.traverse(tree.get(dependencyList), new ConstraintProperties()); + Collection Constraints = FEATUREMODEL.get(dependencyList).getConstraints(); + int atomsSum = 0; + + Iterator constraintIterator = Constraints.iterator(); + while (constraintIterator.hasNext()) { + atomsSum = atomsSum + + Trees.traverse( + constraintIterator.next().getFormula(), + new ConstraintProperties( + COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) + .orElse(0); + } + + return Result.of(atomsSum); } } diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java new file mode 100644 index 00000000..a545da15 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import de.featjar.base.computation.*; +import de.featjar.feature.model.FeatureModel; +import de.featjar.formula.structure.IFormula; +import de.featjar.formula.structure.connective.*; +import de.featjar.formula.structure.predicate.Literal; +import org.junit.jupiter.api.Test; + +public class ConstraintPropertiesTest { + // FeatJAR.(); + // FeatureJAR.log(); + + @Test + public void AtomsTest() { + FeatureModel featureModel = new FeatureModel(); + Literal literal1 = new Literal("o"); + literal1.setPositive(true); + IFormula tree1 = new And( + new Literal("a"), new Literal("b"), new Literal("x"), new Or(literal1, new Literal(false, "i"))); + IFormula tree2 = new And(new Literal("a"), new Or(literal1, new Literal(false, "i"))); + featureModel.addConstraint(tree1); + featureModel.addConstraint(tree2); + int compuational = Computations.of(featureModel) + .map(ComputeConstraintProperties::new) + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .compute(); + tree1.print(); + assertEquals(0, compuational); + } +} From 9b89176189352ac628ce546696a4d8e7de2149ee Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 11:21:31 +0200 Subject: [PATCH 009/257] feat: "average number of children" implemented + docs and tests for "top features", "leaf features", "tree depth" --- .../model/analysis/SimpleTreeProperties.java | 93 +++++++++---------- .../de/featjar/feature/model/analysis/info.md | 1 - .../visitor/FeatureTreeGroupCounter.java | 69 ++++++++++++++ .../visitor/TreeAvgChildrenCounter.java | 82 ++++++++++++++++ .../{ => visitor}/TreeLeafCounter.java | 2 +- .../analysis/SimpleTreePropertiesTest.java | 65 +++++++++++++ 6 files changed, 263 insertions(+), 49 deletions(-) delete mode 100644 src/main/java/de/featjar/feature/model/analysis/info.md create mode 100644 src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java rename src/main/java/de/featjar/feature/model/analysis/{ => visitor}/TreeLeafCounter.java (97%) create mode 100644 src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index 6fe4ade2..b01e4e33 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -5,79 +5,78 @@ import de.featjar.base.tree.Trees; import de.featjar.base.tree.visitor.TreeDepthCounter; import de.featjar.feature.model.*; +import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; +import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; import java.util.List; public class SimpleTreeProperties { - public int topFeatures(IFeatureTree tree) { - - return tree.getRoot().getChildrenCount(); // do groups count as features? - } - - /* - - public int leafFeaturesRecursive(IFeatureTree currentNode) { - - List children = currentNode.getChildren(); - if (children.isEmpty()) { - return 1; - } - - int result = 0; - for (IFeatureTree child : children) { - result += leafFeaturesRecursive(child); - } - return result; + /** + * Automatically finds the root of the given subtree. + * @param tree: feature tree (whose root will be found automatically) + * @return number of features directly below the root of this tree. + */ + public Result topFeatures(IFeatureTree tree) { + int childrenCount = tree.getRoot().getChildrenCount(); + return Result.of(childrenCount); } - public int leafFeaturesStarter(IFeatureTree tree) { - return this.leafFeaturesRecursive(tree.getRoot()); + /** + * @param tree: feature tree + * @return the number of features that have no child features + */ + public Result leafFeaturesCounter(IFeatureTree tree) { + return Trees.traverse(tree, new TreeLeafCounter()); } + /** + * @param tree: feature tree + * @return tree depth, meaning the longest path from this subtree's root to its most distant leaf node */ - - public int leafFeaturesCounter(IFeatureTree tree) { - Result traverseResult = Trees.traverse(tree, new TreeLeafCounter()); - return traverseResult.get(); + public Result treeDepth(IFeatureTree tree) { + TreeDepthCounter visitor = new TreeDepthCounter(); + return Trees.traverse(tree,visitor); } - - public int treeDepth(IFeatureTree tree) { - TreeDepthCounter visitor = new TreeDepthCounter(); - Result result = Trees.traverse(tree.getRoot(),visitor); - return result.get(); + /** + * @param tree: feature tree + * @return average number of children that each node in the tree has, rounded to integer. + */ + public Result avgNumberOfChildren(IFeatureTree tree) { + TreeAvgChildrenCounter visitor = new TreeAvgChildrenCounter(); + return Trees.traverse(tree,visitor); } - public void testMethode() { + // work in progress + public void groupDistribution() { + // build a sample tree FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); IFeatureTree rootTree = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - //rootTree.mutate().toAndGroup(); + rootTree.mutate().toAlternativeGroup(); + IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); - - /* - TreePrinter visitor = new TreePrinter(); - Result traverseResult = Trees.traverse(rootTree, visitor); - System.out.println(traverseResult.get()); - */ - - // int depth = treeDepth(rootTree); - int leafCount = leafFeaturesCounter(rootTree); - System.out.println(leafCount); - + IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); + IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); + + // check subtree for groups + List children = rootTree.getChildrenGroups(); + for (FeatureTree.Group child : children) { + boolean isAnd = child.isAlternative(); + System.out.println(isAnd); + } } public static void main(String[] args){ - + // still have to make all the functions return Results, not ints + // still have to write docs SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); - simpleTreeProperties.testMethode(); + simpleTreeProperties.groupDistribution(); } - } diff --git a/src/main/java/de/featjar/feature/model/analysis/info.md b/src/main/java/de/featjar/feature/model/analysis/info.md deleted file mode 100644 index 16dc75b2..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/info.md +++ /dev/null @@ -1 +0,0 @@ -example file. Put your classes here. \ No newline at end of file diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java new file mode 100644 index 00000000..b11ae520 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ + +package de.featjar.feature.model.analysis.visitor; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; + +import java.util.List; + +/** + * Counts the share of groups found in the given feature tree, in order: Or, And, Alternate. + * + * @author Sebastian Krieter + */ +public class FeatureTreeGroupCounter implements ITreeVisitor, Integer> { + private Class> terminalClass = null; + private int andGroupCount = 0; + private int orGroupCount = 0; + private int altGroupCount = 0; + private int leafCount = 0; + + public Class> getTerminalClass() { + return terminalClass; + } + + public void setTerminalClass(Class> terminalClass) { + this.terminalClass = terminalClass; + } + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + + + + return TraversalAction.CONTINUE; + + } + + @Override + public void reset() { + leafCount = 0; + } + + @Override + public Result getResult() { + return Result.of(leafCount); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java new file mode 100644 index 00000000..8a499379 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ + +package de.featjar.feature.model.analysis.visitor; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; + +import java.util.List; + +/** + * Calculates the average amount of children per node in the tree. + * Returns 0 if tree has no nodes. + * + * @author Sebastian Krieter + */ +public class TreeAvgChildrenCounter implements ITreeVisitor, Integer> { + private Class> terminalClass = null; + private int nodeCount = 0; + private int childCount = 0; + + public Class> getTerminalClass() { + return terminalClass; + } + + public void setTerminalClass(Class> terminalClass) { + this.terminalClass = terminalClass; + } + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + + nodeCount++; + childCount += node.getChildrenCount(); + + // if only nodes with children should be counted + /* + if (node.hasChildren()) { + nodeCount++; + childCount += node.getChildrenCount(); + } + */ + + return TraversalAction.CONTINUE; + } + + @Override + public void reset() { + nodeCount = 0; + childCount = 0; + } + + @Override + public Result getResult() { + // currently uses int because float and double are illegal inputs for Result.of() + int result = 0; + if (nodeCount > 0) { + result = childCount /nodeCount; + } + return Result.of(result); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java similarity index 97% rename from src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java rename to src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java index 3ba95539..0e89d3c7 100644 --- a/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java @@ -19,7 +19,7 @@ * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java new file mode 100644 index 00000000..499a8a1d --- /dev/null +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -0,0 +1,65 @@ +package de.featjar.feature.model.analysis; +import static org.junit.jupiter.api.Assertions.*; + +import de.featjar.Common; +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.FeatureTree; +import de.featjar.feature.model.IFeature; +import de.featjar.feature.model.IFeatureTree; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class SimpleTreePropertiesTest extends Common { + SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); + IFeatureTree smallTree = generateSmallTree(); + + /** + * Creates a tree with a root node that has 1 child, and this child has 2 more children + * @return a small feature tree for testing purposes + */ + public IFeatureTree generateSmallTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + rootTree.mutate().toAlternativeGroup(); + + IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); + IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); + IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); + IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); + IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); + IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); + + + return rootTree; + } + + @Test + void testTopFeatures() { + int rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); + assertEquals(1, rootChildren); + } + + @Test + void testLeafFeaturesCounter() { + int leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); + assertEquals(2, leaves); + } + + @Test + void testTreeDepth() { + int depth = simpleTreeProperties.treeDepth(smallTree).get(); + assertEquals(3, depth); + } + + @Test + void testAvgNumberOfChildren() { + int average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); + assertEquals(0, average); + } + + +} From c0b78e2fc9776db0387b842376c10ca5d50b5c0c Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 14:12:06 +0200 Subject: [PATCH 010/257] feat: "group distribution" implemented. --- .../model/analysis/SimpleTreeProperties.java | 42 +++++------------- .../visitor/FeatureTreeGroupCounter.java | 43 ++++++++++++------- .../visitor/TreeAvgChildrenCounter.java | 16 +++---- .../analysis/SimpleTreePropertiesTest.java | 17 +++++--- 4 files changed, 54 insertions(+), 64 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index b01e4e33..2da439f2 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -1,14 +1,14 @@ package de.featjar.feature.model.analysis; import de.featjar.base.data.Result; -import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.tree.Trees; import de.featjar.base.tree.visitor.TreeDepthCounter; import de.featjar.feature.model.*; +import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; -import java.util.List; +import java.util.HashMap; public class SimpleTreeProperties { @@ -43,40 +43,18 @@ public Result treeDepth(IFeatureTree tree) { * @param tree: feature tree * @return average number of children that each node in the tree has, rounded to integer. */ - public Result avgNumberOfChildren(IFeatureTree tree) { + public Result avgNumberOfChildren(IFeatureTree tree) { TreeAvgChildrenCounter visitor = new TreeAvgChildrenCounter(); return Trees.traverse(tree,visitor); } - // work in progress - public void groupDistribution() { - // build a sample tree - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree rootTree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - rootTree.mutate().toAlternativeGroup(); - - IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); - IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); - IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); - IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); - IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); - - // check subtree for groups - List children = rootTree.getChildrenGroups(); - for (FeatureTree.Group child : children) { - boolean isAnd = child.isAlternative(); - System.out.println(isAnd); - } - + /** Counts the number of different groups in this tree. + * @param tree: feature tree + * @return hashmap with the String keys "AlternativeGroup", "OrGroup" and "AndGroup" to get the respective counts + */ + public Result> groupDistribution(IFeatureTree tree) { + FeatureTreeGroupCounter visitor = new FeatureTreeGroupCounter(); + return Trees.traverse(tree,visitor); } - public static void main(String[] args){ - // still have to make all the functions return Results, not ints - // still have to write docs - SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); - simpleTreeProperties.groupDistribution(); - - } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index b11ae520..e134062f 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -24,7 +24,10 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.feature.model.FeatureTree; +import de.featjar.feature.model.IFeatureTree; +import java.util.HashMap; import java.util.List; /** @@ -32,38 +35,48 @@ * * @author Sebastian Krieter */ -public class FeatureTreeGroupCounter implements ITreeVisitor, Integer> { - private Class> terminalClass = null; - private int andGroupCount = 0; - private int orGroupCount = 0; - private int altGroupCount = 0; - private int leafCount = 0; +public class FeatureTreeGroupCounter implements ITreeVisitor, HashMap> { + private Class> terminalClass = null; + int altCounter = 0, orCounter = 0, andCounter = 0; - public Class> getTerminalClass() { + public Class> getTerminalClass() { return terminalClass; } - public void setTerminalClass(Class> terminalClass) { + public void setTerminalClass(Class> terminalClass) { this.terminalClass = terminalClass; } @Override - public TraversalAction firstVisit(List> path) { - final ITree node = ITreeVisitor.getCurrentNode(path); - + public TraversalAction firstVisit(List> path) { + final IFeatureTree tree = (IFeatureTree) ITreeVisitor.getCurrentNode(path); + for (FeatureTree.Group group : tree.getChildrenGroups()) { + if (group.isAlternative()) { + altCounter++; + } else if (group.isOr()) { + orCounter++; + } else if (group.isAnd()) { + andCounter++; + } + } return TraversalAction.CONTINUE; - } @Override public void reset() { - leafCount = 0; + altCounter = 0; + orCounter = 0; + andCounter = 0; } @Override - public Result getResult() { - return Result.of(leafCount); + public Result> getResult() { + HashMap countedGroups = new HashMap<>(); + countedGroups.put("AlternativeGroup", altCounter); + countedGroups.put("OrGroup", orCounter); + countedGroups.put("AndGroup", andCounter); + return Result.of(countedGroups); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index 8a499379..ceb3eb7c 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -33,7 +33,7 @@ * * @author Sebastian Krieter */ -public class TreeAvgChildrenCounter implements ITreeVisitor, Integer> { +public class TreeAvgChildrenCounter implements ITreeVisitor, Float> { private Class> terminalClass = null; private int nodeCount = 0; private int childCount = 0; @@ -50,16 +50,10 @@ public void setTerminalClass(Class> terminalClass) { public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); + nodeCount++; childCount += node.getChildrenCount(); - // if only nodes with children should be counted - /* - if (node.hasChildren()) { - nodeCount++; - childCount += node.getChildrenCount(); - } - */ return TraversalAction.CONTINUE; } @@ -71,11 +65,11 @@ public void reset() { } @Override - public Result getResult() { + public Result getResult() { // currently uses int because float and double are illegal inputs for Result.of() - int result = 0; + float result = 0; if (nodeCount > 0) { - result = childCount /nodeCount; + result = (float) childCount /nodeCount; } return Result.of(result); } diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 499a8a1d..f7cd86b6 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -4,13 +4,12 @@ import de.featjar.Common; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; import org.junit.jupiter.api.Test; -import java.util.List; +import java.util.HashMap; public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); @@ -20,7 +19,7 @@ public class SimpleTreePropertiesTest extends Common { * Creates a tree with a root node that has 1 child, and this child has 2 more children * @return a small feature tree for testing purposes */ - public IFeatureTree generateSmallTree() { + private IFeatureTree generateSmallTree() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); IFeatureTree rootTree = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); @@ -33,7 +32,6 @@ public IFeatureTree generateSmallTree() { IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); - return rootTree; } @@ -57,9 +55,16 @@ void testTreeDepth() { @Test void testAvgNumberOfChildren() { - int average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); - assertEquals(0, average); + float average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); + assertEquals(0.75, average); } + @Test + void testGroupDistribution() { + HashMap groupCounts = simpleTreeProperties.groupDistribution(smallTree).get(); + assertEquals(1, groupCounts.get("AlternativeGroup")); + assertEquals(3, groupCounts.get("AndGroup")); + assertEquals(0, groupCounts.get("OrGroup")); + } } From 20530573ad75b6f4e3e843f27904c645dd61413c Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 14:57:05 +0200 Subject: [PATCH 011/257] refactor: deleted unnecessary code copied from templates --- .../analysis/visitor/FeatureTreeGroupCounter.java | 9 --------- .../analysis/visitor/TreeAvgChildrenCounter.java | 14 -------------- .../model/analysis/visitor/TreeLeafCounter.java | 9 --------- 3 files changed, 32 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index e134062f..a9e4c633 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -36,17 +36,8 @@ * @author Sebastian Krieter */ public class FeatureTreeGroupCounter implements ITreeVisitor, HashMap> { - private Class> terminalClass = null; int altCounter = 0, orCounter = 0, andCounter = 0; - public Class> getTerminalClass() { - return terminalClass; - } - - public void setTerminalClass(Class> terminalClass) { - this.terminalClass = terminalClass; - } - @Override public TraversalAction firstVisit(List> path) { final IFeatureTree tree = (IFeatureTree) ITreeVisitor.getCurrentNode(path); diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index ceb3eb7c..9b632406 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -34,27 +34,14 @@ * @author Sebastian Krieter */ public class TreeAvgChildrenCounter implements ITreeVisitor, Float> { - private Class> terminalClass = null; private int nodeCount = 0; private int childCount = 0; - public Class> getTerminalClass() { - return terminalClass; - } - - public void setTerminalClass(Class> terminalClass) { - this.terminalClass = terminalClass; - } - @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); - - nodeCount++; childCount += node.getChildrenCount(); - - return TraversalAction.CONTINUE; } @@ -66,7 +53,6 @@ public void reset() { @Override public Result getResult() { - // currently uses int because float and double are illegal inputs for Result.of() float result = 0; if (nodeCount > 0) { result = (float) childCount /nodeCount; diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java index 0e89d3c7..a6d37e80 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java @@ -34,17 +34,8 @@ * @author Sebastian Krieter */ public class TreeLeafCounter implements ITreeVisitor, Integer> { - private Class> terminalClass = null; private int leafCount = 0; - public Class> getTerminalClass() { - return terminalClass; - } - - public void setTerminalClass(Class> terminalClass) { - this.terminalClass = terminalClass; - } - @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); From 144c236f2e186e50889908d27acb74ef704b5fe9 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 14:57:40 +0200 Subject: [PATCH 012/257] test: work towards a tree generator that maps onto the example one from the software product line test course --- .../analysis/SimpleTreePropertiesTest.java | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index f7cd86b6..c426aeea 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -4,6 +4,7 @@ import de.featjar.Common; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; @@ -28,13 +29,59 @@ private IFeatureTree generateSmallTree() { IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); - IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); + childTree1.mutate().addFeatureBelow(childFeature2); IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); - IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); + childTree1.mutate().addFeatureBelow(childFeature3); return rootTree; } + private IFeatureTree generateMediumTree() { + // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree treeRoot = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); + + IFeature featureAPI = featureModel.mutate().addFeature("API"); + IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); + treeAPI.isMandatory(); + // treeRoot.addChild(treeAPI); + + IFeature featureGet = featureModel.mutate().addFeature("Get"); + IFeatureTree treeGet = treeAPI.mutate().addFeatureBelow(featureGet); + //treeAPI.addChild(treeGet); + + IFeature featurePut = featureModel.mutate().addFeature("Put"); + IFeatureTree treePut = treeAPI.mutate().addFeatureBelow(featurePut); + //treeAPI.addChild(treePut); + + IFeature featureDelete = featureModel.mutate().addFeature("Delete"); + IFeatureTree treeDelete = treeAPI.mutate().addFeatureBelow(featureDelete); + //treeAPI.addChild(treeDelete); + treeAPI.mutate().toOrGroup(); + + IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); + IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); + treeTransactions.isOptional(); + + IFeature featureOS = featureModel.mutate().addFeature("OS"); + IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); + treeOS.isMandatory(); + //treeRoot.addChild(treeOS); + + IFeature featureWindows = featureModel.mutate().addFeature("Windows"); + IFeatureTree treeWindows = treeOS.mutate().addFeatureBelow(featureWindows); + //treeOS.addChild(treeWindows); + + IFeature featureLinux = featureModel.mutate().addFeature("Linux"); + IFeatureTree treeLinux = treeOS.mutate().addFeatureBelow(featureLinux); + treeOS.mutate().toAlternativeGroup(); + //treeOS.addChild(treeLinux); + + return treeRoot; + + } + @Test void testTopFeatures() { int rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); @@ -67,4 +114,28 @@ void testGroupDistribution() { assertEquals(0, groupCounts.get("OrGroup")); } + @Test + // to be deleted. This is to find out why everything in our tests has an and group + void smallTest() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + + HashMap groupCounts = simpleTreeProperties.groupDistribution(rootTree).get(); + System.out.println(groupCounts); + + for (FeatureTree.Group group : rootTree.getChildrenGroups()) { + System.out.println(group.isAnd()); + } + System.out.println(); + + } + + @Test + void mediumTest() { + IFeatureTree tree = generateMediumTree(); + HashMap groupCounts = simpleTreeProperties.groupDistribution(tree).get(); + System.out.println(groupCounts); + } + } From e63d6fccdb335d51c1d168b9a7c7245301e99c7f Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 2 Oct 2025 15:06:35 +0200 Subject: [PATCH 013/257] feat: Added functionality for computing FeatureDensity, OperatorDistribution and AverageConstraintSize including simple tests --- .../model/analysis/AverageConstraint.java | 23 +++++++ ...sFeatureShare.java => FeatureDensity.java} | 18 +++-- .../model/analysis/OperatorDistribution.java | 56 ++++++++++++++++ .../computation/ComputeAverageConstraint.java | 65 ++++++++++++++++++ .../computation/ComputeFeatureDensity.java | 63 ++++++++++++++++++ .../ComputeOperatorDistribution.java | 66 +++++++++++++++++++ .../computation/ConstraintPropertiesTest.java | 59 ++++++++++++++--- 7 files changed, 337 insertions(+), 13 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java rename src/main/java/de/featjar/feature/model/analysis/{ConstraintPropertiesFeatureShare.java => FeatureDensity.java} (68%) create mode 100644 src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java create mode 100644 src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java create mode 100644 src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java create mode 100644 src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java diff --git a/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java b/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java new file mode 100644 index 00000000..1764da7a --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +public class AverageConstraint {} diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java similarity index 68% rename from src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java rename to src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java index 10e3502a..68786bfa 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java +++ b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java @@ -23,23 +23,31 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.formula.structure.term.value.Variable; +import java.util.HashSet; import java.util.List; +import java.util.Set; -public class ConstraintPropertiesFeatureShare implements ITreeVisitor, Integer> { +public class FeatureDensity implements ITreeVisitor, Set> { + private Set containedFeatures; @Override public TraversalAction firstVisit(List> path) { - + final ITree node = ITreeVisitor.getCurrentNode(path); + if (node instanceof Variable) { + Variable nodeVar = (Variable) node; + containedFeatures.add(nodeVar.getName()); + } return TraversalAction.CONTINUE; } @Override - public Result getResult() { - return Result.of(0); + public Result> getResult() { + return Result.of(containedFeatures); } @Override public void reset() { - ; + containedFeatures = new HashSet(); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java new file mode 100644 index 00000000..446c86af --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.formula.structure.connective.IConnective; +import java.util.HashMap; +import java.util.List; + +public class OperatorDistribution implements ITreeVisitor, HashMap> { + HashMap operatorCountMap = new HashMap(); + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + if (node instanceof IConnective) { + String nodeKey = node.getClass().getSimpleName(); + if (!operatorCountMap.containsKey(nodeKey)) { + operatorCountMap.put(nodeKey, 1); + } else { + operatorCountMap.replace(nodeKey, operatorCountMap.get(nodeKey) + 1); + } + } + return TraversalAction.CONTINUE; + } + + @Override + public Result> getResult() { + return Result.of(operatorCountMap); + } + + @Override + public void reset() { + operatorCountMap.clear(); + } +} diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java new file mode 100644 index 00000000..171d4ca0 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Computations; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IConstraint; +import de.featjar.feature.model.analysis.ConstraintProperties; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class ComputeAverageConstraint extends AComputation { + protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + + public ComputeAverageConstraint(IComputation featureModel) { + super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + FeatureModel featureModel = FEATUREMODEL.get(dependencyList); + Collection Constraints = featureModel.getConstraints(); + int atomsSum = 0; + + Iterator constraintIterator = Constraints.iterator(); + while (constraintIterator.hasNext()) { + atomsSum = atomsSum + + Trees.traverse( + constraintIterator.next().getFormula(), + new ConstraintProperties( + COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) + .orElse(0); + } + System.out.println(atomsSum + " " + featureModel.getConstraints().size()); + return Result.of( + (float) atomsSum / (float) featureModel.getConstraints().size()); + } +} diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java new file mode 100644 index 00000000..b50d76cc --- /dev/null +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IConstraint; +import de.featjar.feature.model.analysis.FeatureDensity; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public class ComputeFeatureDensity extends AComputation { + protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + + public ComputeFeatureDensity(IComputation featureModel) { + super(featureModel); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + FeatureModel featureModel = FEATUREMODEL.get(dependencyList); + Collection Constraints = featureModel.getConstraints(); + Set unionSet = new HashSet(); + + Iterator constraintIterator = Constraints.iterator(); + while (constraintIterator.hasNext()) { + unionSet.addAll(Trees.traverse(constraintIterator.next().getFormula(), new FeatureDensity()) + .orElse(Collections.emptySet())); + } + + System.out.println( + "" + unionSet.size() + FEATUREMODEL.get(dependencyList).getNumberOfFeatures()); + return Result.of((float) unionSet.size() + / (float) FEATUREMODEL.get(dependencyList).getNumberOfFeatures()); + } +} diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java new file mode 100644 index 00000000..698a74d5 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IConstraint; +import de.featjar.feature.model.analysis.OperatorDistribution; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class ComputeOperatorDistribution extends AComputation> { + protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + + public ComputeOperatorDistribution(IComputation featureModel) { + super(featureModel); + } + + @Override + public Result> compute(List dependencyList, Progress progress) { + FeatureModel featureModel = FEATUREMODEL.get(dependencyList); + Collection Constraints = featureModel.getConstraints(); + HashMap operatorCountMap = new HashMap(); + Iterator constraintIterator = Constraints.iterator(); + + while (constraintIterator.hasNext()) { + HashMap currentOperatorCountMap = Trees.traverse( + constraintIterator.next().getFormula(), new OperatorDistribution()) + .orElse(new HashMap()); + currentOperatorCountMap.forEach((key, value) -> { + if (operatorCountMap.containsKey(key)) { + operatorCountMap.replace(key, operatorCountMap.get(key) + value); + } else { + operatorCountMap.put(key, value); + } + }); + } + + return Result.of(operatorCountMap); + } +} diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index a545da15..d236f302 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -27,6 +27,7 @@ import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.*; import de.featjar.formula.structure.predicate.Literal; +import java.util.HashMap; import org.junit.jupiter.api.Test; public class ConstraintPropertiesTest { @@ -34,20 +35,62 @@ public class ConstraintPropertiesTest { // FeatureJAR.log(); @Test - public void AtomsTest() { + public void atomsTest() { + FeatureModel featureModel = createFeatureModel(); + int compuational = Computations.of(featureModel) + .map(ComputeConstraintProperties::new) + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .compute(); + assertEquals(0, compuational); + } + + @Test + public void featureDensityTest() { + FeatureModel featureModel = createFeatureModel(); + float computational = + Computations.of(featureModel).map(ComputeFeatureDensity::new).compute(); + assertEquals((float) 5 / (float) 6, computational); + } + + @Test + public void operatorDensityTest() { + FeatureModel featureModel = createFeatureModel(); + HashMap computational = Computations.of(featureModel) + .map(ComputeOperatorDistribution::new) + .compute(); + System.out.println(computational); + assertEquals(3, computational.get("And")); + assertEquals(2, computational.get("Or")); + } + + @Test + public void AverageConstraint() { + FeatureModel featureModel = createFeatureModel(); + float computational = + Computations.of(featureModel).map(ComputeAverageConstraint::new).compute(); + System.out.println(computational); + assertEquals((float) 8 / (float) 2, computational); + } + + public FeatureModel createFeatureModel() { FeatureModel featureModel = new FeatureModel(); + featureModel.addFeature("o"); + featureModel.addFeature("b"); + featureModel.addFeature("x"); + featureModel.addFeature("a"); + featureModel.addFeature("i"); + featureModel.addFeature("k"); Literal literal1 = new Literal("o"); literal1.setPositive(true); IFormula tree1 = new And( - new Literal("a"), new Literal("b"), new Literal("x"), new Or(literal1, new Literal(false, "i"))); + new And(), + new Literal("a"), + new Literal("b"), + new Literal("x"), + new Or(literal1, new Literal(false, "i"))); IFormula tree2 = new And(new Literal("a"), new Or(literal1, new Literal(false, "i"))); featureModel.addConstraint(tree1); featureModel.addConstraint(tree2); - int compuational = Computations.of(featureModel) - .map(ComputeConstraintProperties::new) - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) - .compute(); - tree1.print(); - assertEquals(0, compuational); + return featureModel; } } From 98cc06038b2d2807c78299771208c14c0b82acaf Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 15:11:38 +0200 Subject: [PATCH 014/257] style: used gradle formatter --- .../model/analysis/SimpleTreeProperties.java | 39 +++++--- .../visitor/FeatureTreeGroupCounter.java | 12 +-- .../visitor/TreeAvgChildrenCounter.java | 14 ++- .../analysis/visitor/TreeLeafCounter.java | 13 +-- .../analysis/SimpleTreePropertiesTest.java | 95 +++++++++++++++---- 5 files changed, 122 insertions(+), 51 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index 2da439f2..c108a66d 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.analysis; import de.featjar.base.data.Result; @@ -7,18 +27,17 @@ import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; - import java.util.HashMap; public class SimpleTreeProperties { /** - * Automatically finds the root of the given subtree. - * @param tree: feature tree (whose root will be found automatically) - * @return number of features directly below the root of this tree. + * @param tree: feature tree + * @return number of features directly below the root of this subtree. */ public Result topFeatures(IFeatureTree tree) { - int childrenCount = tree.getRoot().getChildrenCount(); + // int childrenCount = tree.getRoot().getChildrenCount(); // if we should find a subtree's root automatically + int childrenCount = tree.getChildrenCount(); return Result.of(childrenCount); } @@ -35,8 +54,7 @@ public Result leafFeaturesCounter(IFeatureTree tree) { * @return tree depth, meaning the longest path from this subtree's root to its most distant leaf node */ public Result treeDepth(IFeatureTree tree) { - TreeDepthCounter visitor = new TreeDepthCounter(); - return Trees.traverse(tree,visitor); + return Trees.traverse(tree, new TreeDepthCounter()); } /** @@ -44,8 +62,7 @@ public Result treeDepth(IFeatureTree tree) { * @return average number of children that each node in the tree has, rounded to integer. */ public Result avgNumberOfChildren(IFeatureTree tree) { - TreeAvgChildrenCounter visitor = new TreeAvgChildrenCounter(); - return Trees.traverse(tree,visitor); + return Trees.traverse(tree, new TreeAvgChildrenCounter()); } /** Counts the number of different groups in this tree. @@ -53,8 +70,6 @@ public Result avgNumberOfChildren(IFeatureTree tree) { * @return hashmap with the String keys "AlternativeGroup", "OrGroup" and "AndGroup" to get the respective counts */ public Result> groupDistribution(IFeatureTree tree) { - FeatureTreeGroupCounter visitor = new FeatureTreeGroupCounter(); - return Trees.traverse(tree,visitor); + return Trees.traverse(tree, new FeatureTreeGroupCounter()); } - } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index a9e4c633..25abdf57 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -1,24 +1,23 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-base. + * This file is part of FeatJAR-feature-model. * - * base is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * base is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with base. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ - package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; @@ -26,7 +25,6 @@ import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeatureTree; - import java.util.HashMap; import java.util.List; diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index 9b632406..6943042a 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -1,30 +1,28 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-base. + * This file is part of FeatJAR-feature-model. * - * base is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * base is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with base. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ - package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; - import java.util.List; /** @@ -55,7 +53,7 @@ public void reset() { public Result getResult() { float result = 0; if (nodeCount > 0) { - result = (float) childCount /nodeCount; + result = (float) childCount / nodeCount; } return Result.of(result); } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java index a6d37e80..4e2453fe 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java @@ -1,30 +1,28 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-base. + * This file is part of FeatJAR-feature-model. * - * base is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * base is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with base. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ - package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; - import java.util.List; /** @@ -43,7 +41,6 @@ public TraversalAction firstVisit(List> path) { leafCount++; } return TraversalAction.CONTINUE; - } @Override diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index c426aeea..487964ca 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -1,4 +1,25 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.analysis; + import static org.junit.jupiter.api.Assertions.*; import de.featjar.Common; @@ -7,10 +28,8 @@ import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; - -import org.junit.jupiter.api.Test; - import java.util.HashMap; +import org.junit.jupiter.api.Test; public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); @@ -36,6 +55,44 @@ private IFeatureTree generateSmallTree() { return rootTree; } + private IFeatureTree generateFeatureTestTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + + // features + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + rootTree.mutate().toAndGroup(); + + IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); + IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); + + IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); + IFeatureTree childTree2 = rootTree.mutate().addFeatureBelow(childFeature2); + + IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); + IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); + childTree1.mutate().toAlternativeGroup(); + + IFeature childFeature4 = featureModel.mutate().addFeature("Test4"); + childTree1.mutate().addFeatureBelow(childFeature4); + + IFeature childFeature5 = featureModel.mutate().addFeature("Test5"); + IFeatureTree childTree5 = childTree2.mutate().addFeatureBelow(childFeature5); + childTree2.mutate().toOrGroup(); + + IFeature childFeature6 = featureModel.mutate().addFeature("Test6"); + childTree2.mutate().addFeatureBelow(childFeature6); + + IFeature childFeature7 = featureModel.mutate().addFeature("Test7"); + IFeatureTree childTree7 = rootTree.mutate().addFeatureBelow(childFeature7); + childTree7.mutate().makeMandatory(); + + IFeature childFeature8 = featureModel.mutate().addFeature("Test8"); + childTree3.mutate().addFeatureBelow(childFeature8); + + return rootTree; + } + private IFeatureTree generateMediumTree() { // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); @@ -49,15 +106,15 @@ private IFeatureTree generateMediumTree() { IFeature featureGet = featureModel.mutate().addFeature("Get"); IFeatureTree treeGet = treeAPI.mutate().addFeatureBelow(featureGet); - //treeAPI.addChild(treeGet); + // treeAPI.addChild(treeGet); IFeature featurePut = featureModel.mutate().addFeature("Put"); IFeatureTree treePut = treeAPI.mutate().addFeatureBelow(featurePut); - //treeAPI.addChild(treePut); + // treeAPI.addChild(treePut); IFeature featureDelete = featureModel.mutate().addFeature("Delete"); IFeatureTree treeDelete = treeAPI.mutate().addFeatureBelow(featureDelete); - //treeAPI.addChild(treeDelete); + // treeAPI.addChild(treeDelete); treeAPI.mutate().toOrGroup(); IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); @@ -67,19 +124,18 @@ private IFeatureTree generateMediumTree() { IFeature featureOS = featureModel.mutate().addFeature("OS"); IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); treeOS.isMandatory(); - //treeRoot.addChild(treeOS); + // treeRoot.addChild(treeOS); IFeature featureWindows = featureModel.mutate().addFeature("Windows"); IFeatureTree treeWindows = treeOS.mutate().addFeatureBelow(featureWindows); - //treeOS.addChild(treeWindows); + // treeOS.addChild(treeWindows); IFeature featureLinux = featureModel.mutate().addFeature("Linux"); IFeatureTree treeLinux = treeOS.mutate().addFeatureBelow(featureLinux); treeOS.mutate().toAlternativeGroup(); - //treeOS.addChild(treeLinux); + // treeOS.addChild(treeLinux); return treeRoot; - } @Test @@ -108,7 +164,8 @@ void testAvgNumberOfChildren() { @Test void testGroupDistribution() { - HashMap groupCounts = simpleTreeProperties.groupDistribution(smallTree).get(); + HashMap groupCounts = + simpleTreeProperties.groupDistribution(smallTree).get(); assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(3, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); @@ -121,21 +178,27 @@ void smallTest() { IFeatureTree rootTree = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - HashMap groupCounts = simpleTreeProperties.groupDistribution(rootTree).get(); + HashMap groupCounts = + simpleTreeProperties.groupDistribution(rootTree).get(); System.out.println(groupCounts); for (FeatureTree.Group group : rootTree.getChildrenGroups()) { System.out.println(group.isAnd()); } System.out.println(); - } @Test void mediumTest() { - IFeatureTree tree = generateMediumTree(); - HashMap groupCounts = simpleTreeProperties.groupDistribution(tree).get(); + // IFeatureTree tree = generateMediumTree(); + IFeatureTree tree = generateFeatureTestTree(); + HashMap groupCounts = + simpleTreeProperties.groupDistribution(tree).get(); System.out.println(groupCounts); - } + IFeatureTree tree2 = generateMediumTree(); + HashMap groupCounts2 = + simpleTreeProperties.groupDistribution(tree2).get(); + System.out.println(groupCounts2); + } } From a67f8e59d4c1cbafdfd9103a9a6e6206ccea7dfc Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 2 Oct 2025 15:21:55 +0200 Subject: [PATCH 015/257] feat: further implementation of features from story card WIP --- .../feature/model/cli/PrintStatistics.java | 155 +++++++++++------- 1 file changed, 99 insertions(+), 56 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index e64ba91d..76f3b58e 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -1,22 +1,22 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-formula. + * This file is part of FeatJAR-feature-model. * - * formula is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * formula is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with formula. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ package de.featjar.feature.model.cli; @@ -26,24 +26,12 @@ import de.featjar.base.cli.OptionList; import de.featjar.base.data.Result; import de.featjar.base.io.IO; -import de.featjar.base.io.csv.CSVFile; -import de.featjar.base.tree.Trees; -import de.featjar.formula.cli.PrintCommand.WhitespaceString; -import de.featjar.formula.io.FormulaFormats; -import de.featjar.formula.io.textual.ExpressionSerializer; -import de.featjar.formula.io.textual.Symbols; -import de.featjar.formula.structure.IFormula; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; +import de.featjar.feature.model.IFeatureModel; +import de.featjar.feature.model.io.FeatureModelFormats; import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.Arrays; -import java.util.Locale; +import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.HashMap; - /** * Prints statistics about a provided Feature Model. @@ -51,36 +39,90 @@ * @author Knut & Kilian */ public class PrintStatistics extends ACommand { - - public static final Option PRETTY_PRINT = Option.newFlag("print") - .setDescription("Pretty prints the numbers"); - - public static final Option INPUT_OPTION = Option.newOption("path", Option.PathParser) - .setDescription("Path to input file(s)"); - // .setValidator(Option.PathValidator) - - private HashMap data = new HashMap(); - + + enum FileTypes { + XML, + CSV, + YAML, + JSON + } + + enum AnalysesScope { + ALL, + TREE_RELATED, + CONSTRAINT_RELATED + } + + public static final Option FILE_TYPE = + Option.newEnumOption("type", FileTypes.class).setDescription("Specifies file type"); + + public static final Option ANALYSES_SCOPE = + Option.newEnumOption("scope", AnalysesScope.class).setDescription("Specifies scope of statistics"); + + public static final Option PRETTY_PRINT = + Option.newFlag("pretty").setDescription("Pretty prints the numbers"); + + private HashMap data; + private FileTypes type; + @Override public int run(OptionList optionParser) { - Boolean bool_prettyPrint = optionParser.get(PRETTY_PRINT); - Path path = optionParser.get(INPUT_OPTION); - - - if (!path.equals(null)) { - FeatJAR.log().message(path); - } - if(bool_prettyPrint) { - FeatJAR.log().message("PRETTY"); - } - - FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + messageLog()); - + + // -----------------INPUT-------------------------------------------------- + + // temporary dummy model + // Path path = Paths.get("../formula/src/testFixtures/resources/Automotive02_V1/model.xml"); + + Path path = optionParser.getResult(INPUT_OPTION).orElseThrow(); + Result load = IO.load(path, FeatureModelFormats.getInstance()); + IFeatureModel model = load.orElseThrow(); + + // -----------------COLLECTING STATS-------------------------------------- + + data = collectStats(model); + + // -----------------WRITING TO FILE--------------------------------------- + + if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { + Path outputPath = optionParser.getResult(OUTPUT_OPTION).get(); + // writeTo(outputPath); + } + + // ----------------PRINTING IN CONSOLE------------------------------------ + + if (optionParser.get(PRETTY_PRINT)) { + printStatsPretty(); + } else { + printStats(); + } + return 0; } - - private StringBuilder messageLog() { - + + // temporary for format type output + private void writeTo(Path path) { + switch (type) { + case XML: + // TODO future Story Card: Write to XML + // IO.save(new (), path, new XMLFeatureModelFormat()); + break; + case CSV: + // TODO future Story Card: Write to CSV + break; + case YAML: + // TODO future Story Card: Write to YAML + break; + case JSON: + // TODO future Story Card: Write to JSON + break; + } + } + + private HashMap collectStats(IFeatureModel model) { + + HashMap data = new HashMap(); + + // dummy values, will be handled by functions of other teams data.put("numOfTopFeatures", 3); data.put("numOfLeafFeatures", 12); data.put("treeDepth", 3); @@ -89,21 +131,22 @@ private StringBuilder messageLog() { data.put("numInAltGroups", 5); data.put("numOfAtoms", 8); data.put("avgNumOfAtomsPerConstraints", 4); - + + return data; + } + + public void printStatsPretty() { StringBuilder outputString = new StringBuilder(); - + for (Map.Entry entry : data.entrySet()) { - outputString.append(String.format("%-30s : %s%n", entry.getKey(), entry.getValue())); + outputString.append(String.format("%-30s : %s%n", entry.getKey(), entry.getValue())); } - return outputString; + FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + outputString); } - - private String writeTocsv() { - //Path path = new Path() - //CSVFile file = new CSVFile(path); - return ""; + + public void printStats() { + FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + data); } - @Override public Optional getDescription() { From 7eb3c10a216c2a571c1437e06b370181c6700424 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 15:35:57 +0200 Subject: [PATCH 016/257] test: every test now checks two trees --- .../analysis/SimpleTreePropertiesTest.java | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 487964ca..b0c68571 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -25,7 +25,6 @@ import de.featjar.Common; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; import java.util.HashMap; @@ -34,9 +33,12 @@ public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); IFeatureTree smallTree = generateSmallTree(); + IFeatureTree featureTestTree = generateFeatureTestTree(); + IFeatureTree mediumTree = generateMediumTree(); /** - * Creates a tree with a root node that has 1 child, and this child has 2 more children + * Creates a tree with a root node that has 1 child, and this child has 2 more children. The root starts + * an alternative group * @return a small feature tree for testing purposes */ private IFeatureTree generateSmallTree() { @@ -45,16 +47,17 @@ private IFeatureTree generateSmallTree() { featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); rootTree.mutate().toAlternativeGroup(); - IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); + IFeature childFeature1 = featureModel.mutate().addFeature("Root's Child (in AltGroup)"); IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); + IFeature childFeature2 = featureModel.mutate().addFeature("1st Child of Root's Child"); childTree1.mutate().addFeatureBelow(childFeature2); - IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); + IFeature childFeature3 = featureModel.mutate().addFeature("2nd Child of Root's Child"); childTree1.mutate().addFeatureBelow(childFeature3); return rootTree; } + // stolen from a predefined test private IFeatureTree generateFeatureTestTree() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); @@ -77,7 +80,7 @@ private IFeatureTree generateFeatureTestTree() { childTree1.mutate().addFeatureBelow(childFeature4); IFeature childFeature5 = featureModel.mutate().addFeature("Test5"); - IFeatureTree childTree5 = childTree2.mutate().addFeatureBelow(childFeature5); + childTree2.mutate().addFeatureBelow(childFeature5); childTree2.mutate().toOrGroup(); IFeature childFeature6 = featureModel.mutate().addFeature("Test6"); @@ -102,19 +105,13 @@ private IFeatureTree generateMediumTree() { IFeature featureAPI = featureModel.mutate().addFeature("API"); IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); treeAPI.isMandatory(); - // treeRoot.addChild(treeAPI); IFeature featureGet = featureModel.mutate().addFeature("Get"); - IFeatureTree treeGet = treeAPI.mutate().addFeatureBelow(featureGet); - // treeAPI.addChild(treeGet); - + treeAPI.mutate().addFeatureBelow(featureGet); IFeature featurePut = featureModel.mutate().addFeature("Put"); - IFeatureTree treePut = treeAPI.mutate().addFeatureBelow(featurePut); - // treeAPI.addChild(treePut); - + treeAPI.mutate().addFeatureBelow(featurePut); IFeature featureDelete = featureModel.mutate().addFeature("Delete"); - IFeatureTree treeDelete = treeAPI.mutate().addFeatureBelow(featureDelete); - // treeAPI.addChild(treeDelete); + treeAPI.mutate().addFeatureBelow(featureDelete); treeAPI.mutate().toOrGroup(); IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); @@ -124,16 +121,12 @@ private IFeatureTree generateMediumTree() { IFeature featureOS = featureModel.mutate().addFeature("OS"); IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); treeOS.isMandatory(); - // treeRoot.addChild(treeOS); IFeature featureWindows = featureModel.mutate().addFeature("Windows"); - IFeatureTree treeWindows = treeOS.mutate().addFeatureBelow(featureWindows); - // treeOS.addChild(treeWindows); - + treeOS.mutate().addFeatureBelow(featureWindows); IFeature featureLinux = featureModel.mutate().addFeature("Linux"); - IFeatureTree treeLinux = treeOS.mutate().addFeatureBelow(featureLinux); + treeOS.mutate().addFeatureBelow(featureLinux); treeOS.mutate().toAlternativeGroup(); - // treeOS.addChild(treeLinux); return treeRoot; } @@ -142,24 +135,36 @@ private IFeatureTree generateMediumTree() { void testTopFeatures() { int rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); assertEquals(1, rootChildren); + + rootChildren = simpleTreeProperties.topFeatures(mediumTree).get(); + assertEquals(3, rootChildren); } @Test void testLeafFeaturesCounter() { int leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); assertEquals(2, leaves); + + leaves = simpleTreeProperties.leafFeaturesCounter(mediumTree).get(); + assertEquals(6, leaves); } @Test void testTreeDepth() { int depth = simpleTreeProperties.treeDepth(smallTree).get(); assertEquals(3, depth); + + depth = simpleTreeProperties.treeDepth(mediumTree).get(); + assertEquals(3, depth); } @Test void testAvgNumberOfChildren() { float average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); assertEquals(0.75, average); + + average = simpleTreeProperties.avgNumberOfChildren(mediumTree).get(); + assertTrue(0.888 < average && average < 0.889); } @Test @@ -169,29 +174,17 @@ void testGroupDistribution() { assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(3, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); - } - @Test - // to be deleted. This is to find out why everything in our tests has an and group - void smallTest() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree rootTree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - - HashMap groupCounts = - simpleTreeProperties.groupDistribution(rootTree).get(); - System.out.println(groupCounts); - - for (FeatureTree.Group group : rootTree.getChildrenGroups()) { - System.out.println(group.isAnd()); - } - System.out.println(); + groupCounts = simpleTreeProperties.groupDistribution(mediumTree).get(); + assertEquals(1, groupCounts.get("AlternativeGroup")); + assertEquals(7, groupCounts.get("AndGroup")); + assertEquals(1, groupCounts.get("OrGroup")); } + // temp test regarding and groups @Test void mediumTest() { - // IFeatureTree tree = generateMediumTree(); - IFeatureTree tree = generateFeatureTestTree(); + IFeatureTree tree = featureTestTree; HashMap groupCounts = simpleTreeProperties.groupDistribution(tree).get(); System.out.println(groupCounts); @@ -201,4 +194,15 @@ void mediumTest() { simpleTreeProperties.groupDistribution(tree2).get(); System.out.println(groupCounts2); } + + // temp test regarding and groups + @Test + void minimalAndGroupTest() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree tree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + HashMap groupCounts = + simpleTreeProperties.groupDistribution(tree).get(); + System.out.println(groupCounts); + } } From 587a6499cebedace9eb94d5fa9457841646826bc Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 15:45:47 +0200 Subject: [PATCH 017/257] test: every test now checks two trees --- .../analysis/SimpleTreePropertiesTest.java | 72 +++---------------- 1 file changed, 11 insertions(+), 61 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index b0c68571..2a2a7372 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -33,7 +33,6 @@ public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); IFeatureTree smallTree = generateSmallTree(); - IFeatureTree featureTestTree = generateFeatureTestTree(); IFeatureTree mediumTree = generateMediumTree(); /** @@ -57,47 +56,13 @@ private IFeatureTree generateSmallTree() { return rootTree; } - // stolen from a predefined test - private IFeatureTree generateFeatureTestTree() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - - // features - IFeatureTree rootTree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - rootTree.mutate().toAndGroup(); - - IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); - IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - - IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); - IFeatureTree childTree2 = rootTree.mutate().addFeatureBelow(childFeature2); - - IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); - IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); - childTree1.mutate().toAlternativeGroup(); - - IFeature childFeature4 = featureModel.mutate().addFeature("Test4"); - childTree1.mutate().addFeatureBelow(childFeature4); - - IFeature childFeature5 = featureModel.mutate().addFeature("Test5"); - childTree2.mutate().addFeatureBelow(childFeature5); - childTree2.mutate().toOrGroup(); - - IFeature childFeature6 = featureModel.mutate().addFeature("Test6"); - childTree2.mutate().addFeatureBelow(childFeature6); - - IFeature childFeature7 = featureModel.mutate().addFeature("Test7"); - IFeatureTree childTree7 = rootTree.mutate().addFeatureBelow(childFeature7); - childTree7.mutate().makeMandatory(); - - IFeature childFeature8 = featureModel.mutate().addFeature("Test8"); - childTree3.mutate().addFeatureBelow(childFeature8); - - return rootTree; - } - + /** + * Resulting tree has three nodes under the root. API is mandatory and below it is an or-group with the features + * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. + * Transactions is an optional feature below the root. + * @return a medium-sized feature tree for testing purposes. + */ private IFeatureTree generateMediumTree() { - // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); IFeatureTree treeRoot = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); @@ -105,7 +70,6 @@ private IFeatureTree generateMediumTree() { IFeature featureAPI = featureModel.mutate().addFeature("API"); IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); treeAPI.isMandatory(); - IFeature featureGet = featureModel.mutate().addFeature("Get"); treeAPI.mutate().addFeatureBelow(featureGet); IFeature featurePut = featureModel.mutate().addFeature("Put"); @@ -114,20 +78,19 @@ private IFeatureTree generateMediumTree() { treeAPI.mutate().addFeatureBelow(featureDelete); treeAPI.mutate().toOrGroup(); - IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); - IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); - treeTransactions.isOptional(); - IFeature featureOS = featureModel.mutate().addFeature("OS"); IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); treeOS.isMandatory(); - IFeature featureWindows = featureModel.mutate().addFeature("Windows"); treeOS.mutate().addFeatureBelow(featureWindows); IFeature featureLinux = featureModel.mutate().addFeature("Linux"); treeOS.mutate().addFeatureBelow(featureLinux); treeOS.mutate().toAlternativeGroup(); + IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); + IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); + treeTransactions.isOptional(); + return treeRoot; } @@ -182,20 +145,7 @@ void testGroupDistribution() { } // temp test regarding and groups - @Test - void mediumTest() { - IFeatureTree tree = featureTestTree; - HashMap groupCounts = - simpleTreeProperties.groupDistribution(tree).get(); - System.out.println(groupCounts); - - IFeatureTree tree2 = generateMediumTree(); - HashMap groupCounts2 = - simpleTreeProperties.groupDistribution(tree2).get(); - System.out.println(groupCounts2); - } - - // temp test regarding and groups + // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? @Test void minimalAndGroupTest() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); From 8c8f39a89ac45a3de36e50593a3406171e6d835c Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 15:50:14 +0200 Subject: [PATCH 018/257] test, style: removed temp test --- .../model/analysis/SimpleTreePropertiesTest.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 2a2a7372..c737e07b 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -143,16 +143,4 @@ void testGroupDistribution() { assertEquals(7, groupCounts.get("AndGroup")); assertEquals(1, groupCounts.get("OrGroup")); } - - // temp test regarding and groups - // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? - @Test - void minimalAndGroupTest() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree tree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - HashMap groupCounts = - simpleTreeProperties.groupDistribution(tree).get(); - System.out.println(groupCounts); - } } From c1800caf0a9eb16d60f5750f95a6c02c5074c397 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 16:44:18 +0200 Subject: [PATCH 019/257] refactor --- .../model/analysis/SimpleTreeProperties.java | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index c108a66d..b93916e0 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -1,23 +1,3 @@ -/* - * Copyright (C) 2025 FeatJAR-Development-Team - * - * This file is part of FeatJAR-feature-model. - * - * feature-model is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3.0 of the License, - * or (at your option) any later version. - * - * feature-model is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with feature-model. If not, see . - * - * See for further information. - */ package de.featjar.feature.model.analysis; import de.featjar.base.data.Result; @@ -27,6 +7,7 @@ import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; + import java.util.HashMap; public class SimpleTreeProperties { @@ -72,4 +53,5 @@ public Result avgNumberOfChildren(IFeatureTree tree) { public Result> groupDistribution(IFeatureTree tree) { return Trees.traverse(tree, new FeatureTreeGroupCounter()); } + } From 9591748e2c45ee243e57fa4aaddcedd261fac18d Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 2 Oct 2025 16:44:46 +0200 Subject: [PATCH 020/257] refactor: improved quality of documentation and code --- .../feature/model/cli/PrintStatistics.java | 58 ++++++++++++++----- 1 file changed, 45 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 76f3b58e..f0d0d3a6 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -28,6 +28,7 @@ import de.featjar.base.io.IO; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.io.FeatureModelFormats; +import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @@ -44,7 +45,8 @@ enum FileTypes { XML, CSV, YAML, - JSON + JSON, + TXT } enum AnalysesScope { @@ -53,6 +55,7 @@ enum AnalysesScope { CONSTRAINT_RELATED } + // options as command line arguments public static final Option FILE_TYPE = Option.newEnumOption("type", FileTypes.class).setDescription("Specifies file type"); @@ -63,29 +66,42 @@ enum AnalysesScope { Option.newFlag("pretty").setDescription("Pretty prints the numbers"); private HashMap data; - private FileTypes type; @Override public int run(OptionList optionParser) { // -----------------INPUT-------------------------------------------------- - // temporary dummy model - // Path path = Paths.get("../formula/src/testFixtures/resources/Automotive02_V1/model.xml"); - Path path = optionParser.getResult(INPUT_OPTION).orElseThrow(); Result load = IO.load(path, FeatureModelFormats.getInstance()); IFeatureModel model = load.orElseThrow(); // -----------------COLLECTING STATS-------------------------------------- - data = collectStats(model); + if (optionParser.getResult(ANALYSES_SCOPE).isPresent()) { + data = collectStats(model, optionParser.get(ANALYSES_SCOPE)); + } else { + data = collectStats(model, AnalysesScope.ALL); + } // -----------------WRITING TO FILE--------------------------------------- - if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { - Path outputPath = optionParser.getResult(OUTPUT_OPTION).get(); - // writeTo(outputPath); + // output path & file type specified + if (optionParser.getResult(OUTPUT_OPTION).isPresent() + && optionParser.getResult(FILE_TYPE).isPresent()) { + + try { + writeTo( + optionParser.getResult(OUTPUT_OPTION).get(), + optionParser.getResult(FILE_TYPE).get()); + } catch (IOException e) { + FeatJAR.log().error(e); + } + + // output path specified, but no file type + } else if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { + + FeatJAR.log().warning("Output path provided, but no file type specified."); } // ----------------PRINTING IN CONSOLE------------------------------------ @@ -100,11 +116,11 @@ public int run(OptionList optionParser) { } // temporary for format type output - private void writeTo(Path path) { + private void writeTo(Path path, FileTypes type) throws IOException { switch (type) { case XML: // TODO future Story Card: Write to XML - // IO.save(new (), path, new XMLFeatureModelFormat()); + // Example: IO.save(new (), path, new XMLFeatureModelFormat()); break; case CSV: // TODO future Story Card: Write to CSV @@ -115,13 +131,25 @@ private void writeTo(Path path) { case JSON: // TODO future Story Card: Write to JSON break; + case TXT: + // TODO future Story Card: Write to TXT } } - private HashMap collectStats(IFeatureModel model) { + private HashMap collectStats(IFeatureModel model, AnalysesScope scope) { HashMap data = new HashMap(); + if (scope == AnalysesScope.ALL || scope == AnalysesScope.CONSTRAINT_RELATED) { + + // For Example model.getConstraintInfo() + + } else if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { + + // For Example model.getTreeDepth() + + } + // dummy values, will be handled by functions of other teams data.put("numOfTopFeatures", 3); data.put("numOfLeafFeatures", 12); @@ -136,12 +164,16 @@ private HashMap collectStats(IFeatureModel model) { } public void printStatsPretty() { + FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + buildPrettyStats()); + } + + private StringBuilder buildPrettyStats() { StringBuilder outputString = new StringBuilder(); for (Map.Entry entry : data.entrySet()) { outputString.append(String.format("%-30s : %s%n", entry.getKey(), entry.getValue())); } - FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + outputString); + return outputString; } public void printStats() { From 00f31889fd16991e8c7979bcd5aec061931b0a30 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 16:44:52 +0200 Subject: [PATCH 021/257] feat: can gather analysis for CSV output, can export stats to strings --- .../featjar/feature/model/io/CSVExporter.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/io/CSVExporter.java diff --git a/src/main/java/de/featjar/feature/model/io/CSVExporter.java b/src/main/java/de/featjar/feature/model/io/CSVExporter.java new file mode 100644 index 00000000..b87766de --- /dev/null +++ b/src/main/java/de/featjar/feature/model/io/CSVExporter.java @@ -0,0 +1,91 @@ +package de.featjar.feature.model.io; + +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.base.io.IO; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeature; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.SimpleTreeProperties; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class CSVExporter { + // should probably pull this from the IO Exporter / Importer + private String csv_delimiter = ";"; + + public IFeatureTree makeTree () { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + rootTree.mutate().toAlternativeGroup(); + + IFeature childFeature1 = featureModel.mutate().addFeature("Root's Child (in AltGroup)"); + IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); + IFeature childFeature2 = featureModel.mutate().addFeature("1st Child of Root's Child"); + childTree1.mutate().addFeatureBelow(childFeature2); + IFeature childFeature3 = featureModel.mutate().addFeature("2nd Child of Root's Child"); + childTree1.mutate().addFeatureBelow(childFeature3); + + return rootTree; + } + + // change formatting here + private String roundAndCastToString (int numerator, int denominator) { + return String.format("%.2f", (float) numerator / denominator); + } + + private String roundAndCastToString (float number) { + return String.format("%.2f", number); + } + + public LinkedHashMap gatherStatistics (IFeatureTree tree) { + SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); + + int topFeatures = simpleTreeProperties.topFeatures(tree).get(); + int leafFeatures = simpleTreeProperties.leafFeaturesCounter(tree).get(); + int treeDepth = simpleTreeProperties.treeDepth(tree).get(); + + HashMap groupDistribution = simpleTreeProperties.groupDistribution(tree).get(); + int alternativeGroups = groupDistribution.get("AlternativeGroup"); + int orGroups = groupDistribution.get("OrGroup"); + int andGroups = groupDistribution.get("AndGroup"); + int allGroups = alternativeGroups + orGroups + andGroups; + + float avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); + + // linked map to preserve order for now, not sure if needed + LinkedHashMap map = new LinkedHashMap<>(); + map.put("Features Directly Below Root", topFeatures); + map.put("Features That Have No Child Features", leafFeatures); + map.put("Tree Depth", treeDepth); + map.put("Share of Alternative Groups", roundAndCastToString(alternativeGroups, allGroups)); + map.put("Share of Or-Groups", roundAndCastToString(orGroups, allGroups)); + map.put("Share of And-Groups", roundAndCastToString(andGroups, allGroups)); + map.put("Average Number of Children", roundAndCastToString(avgNumberOfChildren)); + + return map; + } + + public static void main(String[] args){ + CSVExporter csvExporter = new CSVExporter(); + IFeatureTree tree = csvExporter.makeTree(); + LinkedHashMap stats = csvExporter.gatherStatistics(tree); + // outputs the stat titles in order + String firstLine = String.join(csvExporter.csv_delimiter, stats.keySet()); + + // outputs the stat values in order + String secondLine = stats.values().stream() + .map(String::valueOf) // safely converts all objects to String, including nulls + .collect(Collectors.joining(csvExporter.csv_delimiter)); + + System.out.println(firstLine); // prints the column names + System.out.println(secondLine); // prints the stats (for the first tree) + + // missing: actual exporter, bonus options + + } + +} From 3081edbee8ba73ee2e508918828b602185d74dd6 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 2 Oct 2025 16:53:49 +0200 Subject: [PATCH 022/257] test: reshaped the tests --- .../computation/ConstraintPropertiesTest.java | 100 +++++++++++++----- 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index d236f302..08f3dd55 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -20,13 +20,19 @@ */ package de.featjar.feature.model.computation; +import static de.featjar.formula.structure.Expressions.constant; +import static de.featjar.formula.structure.Expressions.integerAdd; +import static de.featjar.formula.structure.Expressions.variable; import static org.junit.jupiter.api.Assertions.assertEquals; import de.featjar.base.computation.*; import de.featjar.feature.model.FeatureModel; import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.*; +import de.featjar.formula.structure.predicate.Equals; import de.featjar.formula.structure.predicate.Literal; +import de.featjar.formula.structure.term.ITerm; +import de.featjar.formula.structure.term.value.Constant; import java.util.HashMap; import org.junit.jupiter.api.Test; @@ -37,11 +43,26 @@ public class ConstraintPropertiesTest { @Test public void atomsTest() { FeatureModel featureModel = createFeatureModel(); - int compuational = Computations.of(featureModel) - .map(ComputeConstraintProperties::new) - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) - .compute(); - assertEquals(0, compuational); + IComputation compuational = Computations.of(featureModel).map(ComputeConstraintProperties::new); + + assertEquals(21, compuational.compute()); + assertEquals( + 3, + compuational + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .compute()); + assertEquals( + 18, + compuational + .set(ComputeConstraintProperties.COUNTCONSTANTS, Boolean.FALSE) + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.TRUE) + .compute()); + assertEquals( + 0, + compuational + .set(ComputeConstraintProperties.COUNTCONSTANTS, Boolean.FALSE) + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .compute()); } @Test @@ -49,9 +70,9 @@ public void featureDensityTest() { FeatureModel featureModel = createFeatureModel(); float computational = Computations.of(featureModel).map(ComputeFeatureDensity::new).compute(); - assertEquals((float) 5 / (float) 6, computational); + assertEquals((float) 6.0 / (float) 7.0, computational); } - + // operator((and,4), (or, 3), (not, 2), (implies, 3)) @Test public void operatorDensityTest() { FeatureModel featureModel = createFeatureModel(); @@ -59,8 +80,10 @@ public void operatorDensityTest() { .map(ComputeOperatorDistribution::new) .compute(); System.out.println(computational); - assertEquals(3, computational.get("And")); - assertEquals(2, computational.get("Or")); + assertEquals(4, computational.get("And")); + assertEquals(3, computational.get("Or")); + assertEquals(2, computational.get("Not")); + assertEquals(3, computational.get("Implies")); } @Test @@ -69,28 +92,57 @@ public void AverageConstraint() { float computational = Computations.of(featureModel).map(ComputeAverageConstraint::new).compute(); System.out.println(computational); - assertEquals((float) 8 / (float) 2, computational); + assertEquals(21.0 / 3.0, computational); } public FeatureModel createFeatureModel() { FeatureModel featureModel = new FeatureModel(); - featureModel.addFeature("o"); - featureModel.addFeature("b"); - featureModel.addFeature("x"); + + // add Features (7) featureModel.addFeature("a"); + featureModel.addFeature("b"); + featureModel.addFeature("c"); featureModel.addFeature("i"); featureModel.addFeature("k"); - Literal literal1 = new Literal("o"); - literal1.setPositive(true); - IFormula tree1 = new And( - new And(), - new Literal("a"), - new Literal("b"), - new Literal("x"), - new Or(literal1, new Literal(false, "i"))); - IFormula tree2 = new And(new Literal("a"), new Or(literal1, new Literal(false, "i"))); - featureModel.addConstraint(tree1); - featureModel.addConstraint(tree2); + featureModel.addFeature("o"); + featureModel.addFeature("x"); + + // define Features as literals + Literal literalA = new Literal("a"); + Literal literalB = new Literal("b"); + Literal literalC = new Literal("c"); + Literal literalI = new Literal("i"); + Literal literalK = new Literal("k"); + Literal literalO = new Literal("o"); + Literal literalX = new Literal("x"); + + // set some variables or literals + literalO.setPositive(true); + literalB.setPositive(false); + + // define terms + ITerm termAdd = integerAdd(constant(42L), variable("varA", Long.class)); + ITerm termAddLiteral = integerAdd(constant(42L), new Constant(2L)); + // ITerm termAddLiteral1 = integerAdd(constant(42L), new Constant(literalA, literalA.getClass())); + + // operator((and,4), (or, 3), (not, 2), (implies, 3)) + // define formulas + // 8 literal, 5 operator((and,2), (or, 1), (not, 1), (implies, 1)), Features(a,b,i,k,o) + IFormula formula1 = new And( + literalA, + new Or(literalA, literalB, literalI), + new Not(literalB), + new Implies(literalK, literalO), + new And(literalB)); + // 9 literal, 7 operator((and,2), (or, 2), (not, 1), (implies, 2)), Features(a,b,i,k,o) + IFormula formula2 = new Or(new Implies(formula1, literalO)); + // 1 literal, 0 operator, 3 constants + IFormula formula3 = new Equals(termAdd, termAddLiteral); + + // add full formulas as constraints + featureModel.addConstraint(formula1); + featureModel.addConstraint(formula2); + featureModel.addConstraint(formula3); return featureModel; } } From 162f9a0345420b3beb7ea378be4bef984204a071 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Sat, 4 Oct 2025 18:36:17 +0200 Subject: [PATCH 023/257] test: added tests for a minimal tree with only 1 node, the root. --- .../analysis/SimpleTreePropertiesTest.java | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index c737e07b..dbad012b 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -32,11 +32,20 @@ public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); + IFeatureTree minimalTree = generateMinimalTree(); IFeatureTree smallTree = generateSmallTree(); IFeatureTree mediumTree = generateMediumTree(); /** - * Creates a tree with a root node that has 1 child, and this child has 2 more children. The root starts + * @return bare-bones feature tree with just a root node to test edge cases. + */ + private IFeatureTree generateMinimalTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + return featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + } + + /** + * Creates a feature tree with a root node that has 1 child, and this child has 2 more children. The root starts * an alternative group * @return a small feature tree for testing purposes */ @@ -57,7 +66,7 @@ private IFeatureTree generateSmallTree() { } /** - * Resulting tree has three nodes under the root. API is mandatory and below it is an or-group with the features + * Feature tree with three nodes under the root. API is mandatory and below it is an or-group with the features * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. * Transactions is an optional feature below the root. * @return a medium-sized feature tree for testing purposes. @@ -96,7 +105,12 @@ private IFeatureTree generateMediumTree() { @Test void testTopFeatures() { - int rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); + int rootChildren; + + rootChildren = simpleTreeProperties.topFeatures(minimalTree).get(); + assertEquals(0, rootChildren); + + rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); assertEquals(1, rootChildren); rootChildren = simpleTreeProperties.topFeatures(mediumTree).get(); @@ -105,7 +119,12 @@ void testTopFeatures() { @Test void testLeafFeaturesCounter() { - int leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); + int leaves; + + leaves = simpleTreeProperties.leafFeaturesCounter(minimalTree).get(); + assertEquals(1, leaves); + + leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); assertEquals(2, leaves); leaves = simpleTreeProperties.leafFeaturesCounter(mediumTree).get(); @@ -114,7 +133,12 @@ void testLeafFeaturesCounter() { @Test void testTreeDepth() { - int depth = simpleTreeProperties.treeDepth(smallTree).get(); + int depth; + + depth = simpleTreeProperties.treeDepth(minimalTree).get(); + assertEquals(1, depth); + + depth = simpleTreeProperties.treeDepth(smallTree).get(); assertEquals(3, depth); depth = simpleTreeProperties.treeDepth(mediumTree).get(); @@ -123,7 +147,12 @@ void testTreeDepth() { @Test void testAvgNumberOfChildren() { - float average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); + float average; + + average = simpleTreeProperties.avgNumberOfChildren(minimalTree).get(); + assertEquals(0.0, average); + + average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); assertEquals(0.75, average); average = simpleTreeProperties.avgNumberOfChildren(mediumTree).get(); @@ -132,8 +161,14 @@ void testAvgNumberOfChildren() { @Test void testGroupDistribution() { - HashMap groupCounts = - simpleTreeProperties.groupDistribution(smallTree).get(); + HashMap groupCounts; + + groupCounts = simpleTreeProperties.groupDistribution(minimalTree).get(); + assertEquals(0, groupCounts.get("AlternativeGroup")); + assertEquals(1, groupCounts.get("AndGroup")); + assertEquals(0, groupCounts.get("OrGroup")); + + groupCounts = simpleTreeProperties.groupDistribution(smallTree).get(); assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(3, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); From dc0634f046d0d1ceb49f07455b92e4d562a65eb9 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Sun, 5 Oct 2025 11:17:45 +0200 Subject: [PATCH 024/257] refactor: assigned more specific names to variables and adjusted imports --- ...straintProperties.java => AtomsCount.java} | 11 +++++++-- .../model/analysis/AverageConstraint.java | 23 ------------------- .../model/analysis/FeatureDensity.java | 6 +++++ .../model/analysis/OperatorDistribution.java | 18 ++++++++++----- ...Properties.java => ComputeAtomsCount.java} | 14 +++++++---- .../computation/ComputeAverageConstraint.java | 5 ++-- .../computation/ComputeFeatureDensity.java | 3 --- .../ComputeOperatorDistribution.java | 14 +++++------ .../computation/ConstraintPropertiesTest.java | 12 +++++----- 9 files changed, 51 insertions(+), 55 deletions(-) rename src/main/java/de/featjar/feature/model/analysis/{ConstraintProperties.java => AtomsCount.java} (84%) delete mode 100644 src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java rename src/main/java/de/featjar/feature/model/computation/{ComputeConstraintProperties.java => ComputeAtomsCount.java} (82%) diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java similarity index 84% rename from src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java rename to src/main/java/de/featjar/feature/model/analysis/AtomsCount.java index 55d431b1..31824ee1 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java @@ -27,12 +27,19 @@ import de.featjar.formula.structure.term.value.Variable; import java.util.List; -public class ConstraintProperties implements ITreeVisitor, Integer> { +/** + * Counts the number of used variables and constants in a tree. + * By default, both are counted, but it can be set to count only one of the two. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ +public class AtomsCount implements ITreeVisitor, Integer> { private int atomsCount = 0; private boolean countVariables = true; private boolean countConstants = true; - public ConstraintProperties(boolean countVariables, boolean countConstants) { + public AtomsCount(boolean countVariables, boolean countConstants) { this.countConstants = countConstants; this.countVariables = countVariables; } diff --git a/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java b/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java deleted file mode 100644 index 1764da7a..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2025 FeatJAR-Development-Team - * - * This file is part of FeatJAR-feature-model. - * - * feature-model is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3.0 of the License, - * or (at your option) any later version. - * - * feature-model is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with feature-model. If not, see . - * - * See for further information. - */ -package de.featjar.feature.model.analysis; - -public class AverageConstraint {} diff --git a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java index 68786bfa..fb7aeb40 100644 --- a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java @@ -28,6 +28,12 @@ import java.util.List; import java.util.Set; +/** + * Enumerates the names of all distinct variables occurring in a tree. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class FeatureDensity implements ITreeVisitor, Set> { private Set containedFeatures; diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java index 446c86af..6cd061f3 100644 --- a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -27,18 +27,24 @@ import java.util.HashMap; import java.util.List; +/** + * Counts the the absolute occurrence of different operators in a tree. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class OperatorDistribution implements ITreeVisitor, HashMap> { - HashMap operatorCountMap = new HashMap(); + HashMap operatorCount = new HashMap(); @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); if (node instanceof IConnective) { String nodeKey = node.getClass().getSimpleName(); - if (!operatorCountMap.containsKey(nodeKey)) { - operatorCountMap.put(nodeKey, 1); + if (!operatorCount.containsKey(nodeKey)) { + operatorCount.put(nodeKey, 1); } else { - operatorCountMap.replace(nodeKey, operatorCountMap.get(nodeKey) + 1); + operatorCount.replace(nodeKey, operatorCount.get(nodeKey) + 1); } } return TraversalAction.CONTINUE; @@ -46,11 +52,11 @@ public TraversalAction firstVisit(List> path) { @Override public Result> getResult() { - return Result.of(operatorCountMap); + return Result.of(operatorCount); } @Override public void reset() { - operatorCountMap.clear(); + operatorCount.clear(); } } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java similarity index 82% rename from src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java rename to src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index d49f8b67..35c6916f 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -20,22 +20,26 @@ */ package de.featjar.feature.model.computation; -import de.featjar.base.computation.*; +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Computations; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.feature.model.analysis.ConstraintProperties; +import de.featjar.feature.model.analysis.AtomsCount; import java.util.Collection; import java.util.Iterator; import java.util.List; -public class ComputeConstraintProperties extends AComputation { +public class ComputeAtomsCount extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); - public ComputeConstraintProperties(IComputation featureModel) { + public ComputeAtomsCount(IComputation featureModel) { super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); } @@ -49,7 +53,7 @@ public Result compute(List dependencyList, Progress progress) { atomsSum = atomsSum + Trees.traverse( constraintIterator.next().getFormula(), - new ConstraintProperties( + new AtomsCount( COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) .orElse(0); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 171d4ca0..4e9e4d82 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -29,7 +29,7 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.feature.model.analysis.ConstraintProperties; +import de.featjar.feature.model.analysis.AtomsCount; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -54,11 +54,10 @@ public Result compute(List dependencyList, Progress progress) { atomsSum = atomsSum + Trees.traverse( constraintIterator.next().getFormula(), - new ConstraintProperties( + new AtomsCount( COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) .orElse(0); } - System.out.println(atomsSum + " " + featureModel.getConstraints().size()); return Result.of( (float) atomsSum / (float) featureModel.getConstraints().size()); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java index b50d76cc..ede39092 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -54,9 +54,6 @@ public Result compute(List dependencyList, Progress progress) { unionSet.addAll(Trees.traverse(constraintIterator.next().getFormula(), new FeatureDensity()) .orElse(Collections.emptySet())); } - - System.out.println( - "" + unionSet.size() + FEATUREMODEL.get(dependencyList).getNumberOfFeatures()); return Result.of((float) unionSet.size() / (float) FEATUREMODEL.get(dependencyList).getNumberOfFeatures()); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java index 698a74d5..499031c6 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -45,22 +45,22 @@ public ComputeOperatorDistribution(IComputation featureModel) { public Result> compute(List dependencyList, Progress progress) { FeatureModel featureModel = FEATUREMODEL.get(dependencyList); Collection Constraints = featureModel.getConstraints(); - HashMap operatorCountMap = new HashMap(); + HashMap operatorCount = new HashMap(); Iterator constraintIterator = Constraints.iterator(); while (constraintIterator.hasNext()) { - HashMap currentOperatorCountMap = Trees.traverse( + HashMap currentOperatorCount = Trees.traverse( constraintIterator.next().getFormula(), new OperatorDistribution()) .orElse(new HashMap()); - currentOperatorCountMap.forEach((key, value) -> { - if (operatorCountMap.containsKey(key)) { - operatorCountMap.replace(key, operatorCountMap.get(key) + value); + currentOperatorCount.forEach((key, value) -> { + if (operatorCount.containsKey(key)) { + operatorCount.replace(key, operatorCount.get(key) + value); } else { - operatorCountMap.put(key, value); + operatorCount.put(key, value); } }); } - return Result.of(operatorCountMap); + return Result.of(operatorCount); } } diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index 08f3dd55..aee6b722 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -43,25 +43,25 @@ public class ConstraintPropertiesTest { @Test public void atomsTest() { FeatureModel featureModel = createFeatureModel(); - IComputation compuational = Computations.of(featureModel).map(ComputeConstraintProperties::new); + IComputation compuational = Computations.of(featureModel).map(ComputeAtomsCount::new); assertEquals(21, compuational.compute()); assertEquals( 3, compuational - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.FALSE) .compute()); assertEquals( 18, compuational - .set(ComputeConstraintProperties.COUNTCONSTANTS, Boolean.FALSE) - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.TRUE) + .set(ComputeAtomsCount.COUNTCONSTANTS, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.TRUE) .compute()); assertEquals( 0, compuational - .set(ComputeConstraintProperties.COUNTCONSTANTS, Boolean.FALSE) - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTCONSTANTS, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.FALSE) .compute()); } From d54ebc0f624da2e8a67bdfa58bda1287f54f84e1 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Sun, 5 Oct 2025 11:42:23 +0200 Subject: [PATCH 025/257] chores: resolved star imports to specific imports. removed console prints and temporary comments. --- .../model/computation/ConstraintPropertiesTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index aee6b722..eddd61b3 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -25,10 +25,14 @@ import static de.featjar.formula.structure.Expressions.variable; import static org.junit.jupiter.api.Assertions.assertEquals; -import de.featjar.base.computation.*; +import de.featjar.base.computation.Computations; +import de.featjar.base.computation.IComputation; import de.featjar.feature.model.FeatureModel; import de.featjar.formula.structure.IFormula; -import de.featjar.formula.structure.connective.*; +import de.featjar.formula.structure.connective.And; +import de.featjar.formula.structure.connective.Implies; +import de.featjar.formula.structure.connective.Not; +import de.featjar.formula.structure.connective.Or; import de.featjar.formula.structure.predicate.Equals; import de.featjar.formula.structure.predicate.Literal; import de.featjar.formula.structure.term.ITerm; @@ -37,8 +41,6 @@ import org.junit.jupiter.api.Test; public class ConstraintPropertiesTest { - // FeatJAR.(); - // FeatureJAR.log(); @Test public void atomsTest() { @@ -79,7 +81,6 @@ public void operatorDensityTest() { HashMap computational = Computations.of(featureModel) .map(ComputeOperatorDistribution::new) .compute(); - System.out.println(computational); assertEquals(4, computational.get("And")); assertEquals(3, computational.get("Or")); assertEquals(2, computational.get("Not")); @@ -91,7 +92,6 @@ public void AverageConstraint() { FeatureModel featureModel = createFeatureModel(); float computational = Computations.of(featureModel).map(ComputeAverageConstraint::new).compute(); - System.out.println(computational); assertEquals(21.0 / 3.0, computational); } From 34e7f7c28778fe10bd9608fcc81cd22a7b08667f Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Mon, 6 Oct 2025 10:03:47 +0200 Subject: [PATCH 026/257] docs: added documentation to the classes Visitors and Computational for the formula analysis part. feat: Added counting for True and False instances in the class AtomsCount. test: modified the tests slightly to account for the changes in the class AtomsCount. --- .../feature/model/analysis/AtomsCount.java | 9 +++++++- .../model/analysis/FeatureDensity.java | 1 + .../model/analysis/OperatorDistribution.java | 1 + .../model/computation/ComputeAtomsCount.java | 20 ++++++++++++++++-- .../computation/ComputeAverageConstraint.java | 21 +++++++++++++++++-- .../computation/ComputeFeatureDensity.java | 7 +++++++ .../ComputeOperatorDistribution.java | 7 +++++++ .../computation/ConstraintPropertiesTest.java | 13 ++++++++---- 8 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java index 31824ee1..f2e8f168 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java +++ b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java @@ -23,6 +23,8 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.formula.structure.predicate.False; +import de.featjar.formula.structure.predicate.True; import de.featjar.formula.structure.term.value.Constant; import de.featjar.formula.structure.term.value.Variable; import java.util.List; @@ -30,6 +32,7 @@ /** * Counts the number of used variables and constants in a tree. * By default, both are counted, but it can be set to count only one of the two. + * For further information on its methods see {@link ITreeVisitor} * * @author Mohammad Khair Almekkawi * @author Florian Beese @@ -38,10 +41,12 @@ public class AtomsCount implements ITreeVisitor, Integer> { private int atomsCount = 0; private boolean countVariables = true; private boolean countConstants = true; + private boolean countBoolean = true; - public AtomsCount(boolean countVariables, boolean countConstants) { + public AtomsCount(boolean countVariables, boolean countConstants, boolean countBoolean) { this.countConstants = countConstants; this.countVariables = countVariables; + this.countBoolean = countBoolean; } @Override @@ -51,6 +56,8 @@ public TraversalAction firstVisit(List> path) { atomsCount++; } else if (countVariables && node instanceof Variable) { atomsCount++; + } else if (countBoolean && (node instanceof True || node instanceof False)) { + atomsCount++; } return TraversalAction.CONTINUE; } diff --git a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java index fb7aeb40..b9de92cc 100644 --- a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java @@ -30,6 +30,7 @@ /** * Enumerates the names of all distinct variables occurring in a tree. + * For further information on its methods see {@link ITreeVisitor} * * @author Mohammad Khair Almekkawi * @author Florian Beese diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java index 6cd061f3..50973081 100644 --- a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -29,6 +29,7 @@ /** * Counts the the absolute occurrence of different operators in a tree. + * For further information on its methods see {@link ITreeVisitor} * * @author Mohammad Khair Almekkawi * @author Florian Beese diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index 35c6916f..587ac43f 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -34,13 +34,27 @@ import java.util.Iterator; import java.util.List; +/** + * Call the visitor AtomsCount on all constraints of a feature model to count the terminal expressions. + * It is possible to set the three variables COUNTCONSTANTS, COUNTVARIABLES and COUNTBOOLEAN in order + * to set what should be counted + * For further information on its methods see {@link IComputation} + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + * */ public class ComputeAtomsCount extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); public ComputeAtomsCount(IComputation featureModel) { - super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); + super( + featureModel, + Computations.of(Boolean.TRUE), + Computations.of(Boolean.TRUE), + Computations.of(Boolean.TRUE)); } @Override @@ -54,7 +68,9 @@ public Result compute(List dependencyList, Progress progress) { + Trees.traverse( constraintIterator.next().getFormula(), new AtomsCount( - COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) + COUNTVARIABLES.get(dependencyList), + COUNTCONSTANTS.get(dependencyList), + COUNTBOOLEAN.get(dependencyList))) .orElse(0); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 4e9e4d82..34a5ff9b 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -34,13 +34,28 @@ import java.util.Iterator; import java.util.List; +/** + * Call the visitor AtomsCount on all constraints of a feature model to count the terminal expressions and then + * compute the average in relation to the count of constraints of a feature model. + * It is possible to set the three variables COUNTCONSTANTS, COUNTVARIABLES and COUNTBOOLEAN in order + * to set what should be counted. + * For further information on its methods see {@link IComputation} + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + * */ public class ComputeAverageConstraint extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); public ComputeAverageConstraint(IComputation featureModel) { - super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); + super( + featureModel, + Computations.of(Boolean.TRUE), + Computations.of(Boolean.TRUE), + Computations.of(Boolean.TRUE)); } @Override @@ -55,7 +70,9 @@ public Result compute(List dependencyList, Progress progress) { + Trees.traverse( constraintIterator.next().getFormula(), new AtomsCount( - COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) + COUNTVARIABLES.get(dependencyList), + COUNTCONSTANTS.get(dependencyList), + COUNTBOOLEAN.get(dependencyList))) .orElse(0); } return Result.of( diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java index ede39092..95c71388 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -36,6 +36,13 @@ import java.util.List; import java.util.Set; +/** + * Call the visitor FeatureDensity on all constraints of a feature model to get the density of the used features in the constraints. + * For further information on its methods see {@link IComputation} + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + * */ public class ComputeFeatureDensity extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java index 499031c6..8832cb7c 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -34,6 +34,13 @@ import java.util.Iterator; import java.util.List; +/** + * Call the visitor OperatorDistribution on all constraints of a feature model to get the count of each nonterminal operation(and, or,...). + * For further information on its methods see {@link IComputation} + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + * */ public class ComputeOperatorDistribution extends AComputation> { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index eddd61b3..854a7aed 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -35,6 +35,7 @@ import de.featjar.formula.structure.connective.Or; import de.featjar.formula.structure.predicate.Equals; import de.featjar.formula.structure.predicate.Literal; +import de.featjar.formula.structure.predicate.True; import de.featjar.formula.structure.term.ITerm; import de.featjar.formula.structure.term.value.Constant; import java.util.HashMap; @@ -47,23 +48,26 @@ public void atomsTest() { FeatureModel featureModel = createFeatureModel(); IComputation compuational = Computations.of(featureModel).map(ComputeAtomsCount::new); - assertEquals(21, compuational.compute()); + assertEquals(23, compuational.compute()); assertEquals( 3, compuational .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTBOOLEAN, Boolean.FALSE) .compute()); assertEquals( 18, compuational .set(ComputeAtomsCount.COUNTCONSTANTS, Boolean.FALSE) .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.TRUE) + .set(ComputeAtomsCount.COUNTBOOLEAN, Boolean.FALSE) .compute()); assertEquals( 0, compuational .set(ComputeAtomsCount.COUNTCONSTANTS, Boolean.FALSE) .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTBOOLEAN, Boolean.FALSE) .compute()); } @@ -72,7 +76,7 @@ public void featureDensityTest() { FeatureModel featureModel = createFeatureModel(); float computational = Computations.of(featureModel).map(ComputeFeatureDensity::new).compute(); - assertEquals((float) 6.0 / (float) 7.0, computational); + assertEquals((float) 6 / (float) 7, computational); } // operator((and,4), (or, 3), (not, 2), (implies, 3)) @Test @@ -92,7 +96,7 @@ public void AverageConstraint() { FeatureModel featureModel = createFeatureModel(); float computational = Computations.of(featureModel).map(ComputeAverageConstraint::new).compute(); - assertEquals(21.0 / 3.0, computational); + assertEquals((float) 23 / (float) 3, computational); } public FeatureModel createFeatureModel() { @@ -133,7 +137,8 @@ public FeatureModel createFeatureModel() { new Or(literalA, literalB, literalI), new Not(literalB), new Implies(literalK, literalO), - new And(literalB)); + new And(literalB), + True.INSTANCE); // 9 literal, 7 operator((and,2), (or, 2), (not, 1), (implies, 2)), Features(a,b,i,k,o) IFormula formula2 = new Or(new Implies(formula1, literalO)); // 1 literal, 0 operator, 3 constants From 1e96699884d282fdd69b05c2dbc8c4de74020cad Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 10:30:52 +0200 Subject: [PATCH 027/257] refactor --- .../model/io/{ => csv}/CSVExporter.java | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) rename src/main/java/de/featjar/feature/model/io/{ => csv}/CSVExporter.java (79%) diff --git a/src/main/java/de/featjar/feature/model/io/CSVExporter.java b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java similarity index 79% rename from src/main/java/de/featjar/feature/model/io/CSVExporter.java rename to src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java index b87766de..7c823806 100644 --- a/src/main/java/de/featjar/feature/model/io/CSVExporter.java +++ b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java @@ -1,4 +1,4 @@ -package de.featjar.feature.model.io; +package de.featjar.feature.model.io.csv; import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.io.IO; @@ -6,15 +6,22 @@ import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.SimpleTreeProperties; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.charset.Charset; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.Map; import java.util.stream.Collectors; + +/** + * This is a temp class; the real implementation will be done via IFormat implementation. It will be deleted later. + */ public class CSVExporter { // should probably pull this from the IO Exporter / Importer - private String csv_delimiter = ";"; + public final String DELIMITER = ";"; + public final Charset DEFAULT_CHARSET = IO.DEFAULT_CHARSET; public IFeatureTree makeTree () { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); @@ -57,6 +64,7 @@ public LinkedHashMap gatherStatistics (IFeatureTree tree) { float avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); // linked map to preserve order for now, not sure if needed + // todo: decide whether to make this a variable map, or a ready-to-write map LinkedHashMap map = new LinkedHashMap<>(); map.put("Features Directly Below Root", topFeatures); map.put("Features That Have No Child Features", leafFeatures); @@ -69,22 +77,36 @@ public LinkedHashMap gatherStatistics (IFeatureTree tree) { return map; } + public void export(String[] csvStrings) { + Path path = Paths.get("C:\\Users\\bentu\\Desktop\\myfile.csv"); + + /* + IO.write( + String.join("\n", csvStrings), + path, + DEFAULT_CHARSET + ); + + */ + + } + public static void main(String[] args){ CSVExporter csvExporter = new CSVExporter(); IFeatureTree tree = csvExporter.makeTree(); LinkedHashMap stats = csvExporter.gatherStatistics(tree); // outputs the stat titles in order - String firstLine = String.join(csvExporter.csv_delimiter, stats.keySet()); + String firstLine = String.join(csvExporter.DELIMITER, stats.keySet()); // outputs the stat values in order String secondLine = stats.values().stream() .map(String::valueOf) // safely converts all objects to String, including nulls - .collect(Collectors.joining(csvExporter.csv_delimiter)); + .collect(Collectors.joining(csvExporter.DELIMITER)); System.out.println(firstLine); // prints the column names System.out.println(secondLine); // prints the stats (for the first tree) - // missing: actual exporter, bonus options + // todo iformat, xmlformat } From 8a867eb3fdfd07851b52eadb53c5c32a00faa4fe Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Mon, 6 Oct 2025 13:24:48 +0200 Subject: [PATCH 028/257] docs: added some extra docs. fix: corrected the OperatorDistribution, so that it does not count Reference class --- .../java/de/featjar/feature/model/analysis/AtomsCount.java | 6 ++++++ .../feature/model/analysis/OperatorDistribution.java | 5 ++++- .../feature/model/computation/ComputeAtomsCount.java | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java index f2e8f168..cf37c9b0 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java +++ b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java @@ -43,6 +43,12 @@ public class AtomsCount implements ITreeVisitor, Integer> { private boolean countConstants = true; private boolean countBoolean = true; + /** + * + * @param countVariables decide if Atoms of type variable should be counted + * @param countConstants decide if Atoms of type constants should be counted + * @param countBoolean decide if Atoms of type True or False should be counted + */ public AtomsCount(boolean countVariables, boolean countConstants, boolean countBoolean) { this.countConstants = countConstants; this.countVariables = countVariables; diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java index 50973081..8f97caeb 100644 --- a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -24,6 +24,8 @@ import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.formula.structure.connective.IConnective; +import de.featjar.formula.structure.connective.Reference; + import java.util.HashMap; import java.util.List; @@ -35,12 +37,13 @@ * @author Florian Beese */ public class OperatorDistribution implements ITreeVisitor, HashMap> { + // Saves the count of each operator, where each key is the name of the class of the operator HashMap operatorCount = new HashMap(); @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); - if (node instanceof IConnective) { + if (node instanceof IConnective && ! (node instanceof Reference)) { String nodeKey = node.getClass().getSimpleName(); if (!operatorCount.containsKey(nodeKey)) { operatorCount.put(nodeKey, 1); diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index 587ac43f..44bf884f 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -45,10 +45,14 @@ * */ public class ComputeAtomsCount extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + //COUNTCONSTANTS decide if Atoms of type constants should be counted protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + //COUNTVARIABLES decide if Atoms of type variable should be counted protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + //COUNTBOOLEAN decide if Atoms of type True or False should be counted protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); + public ComputeAtomsCount(IComputation featureModel) { super( featureModel, From 52efcffb02b50b703f7d362d2d0a799af23ea4e1 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 14:06:26 +0200 Subject: [PATCH 029/257] feat: now also counts other potential groups. --- .../model/analysis/visitor/FeatureTreeGroupCounter.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index 25abdf57..6a0fc52b 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -34,7 +34,7 @@ * @author Sebastian Krieter */ public class FeatureTreeGroupCounter implements ITreeVisitor, HashMap> { - int altCounter = 0, orCounter = 0, andCounter = 0; + int altCounter = 0, orCounter = 0, andCounter = 0, otherCounter = 0; @Override public TraversalAction firstVisit(List> path) { @@ -47,6 +47,8 @@ public TraversalAction firstVisit(List> path) { orCounter++; } else if (group.isAnd()) { andCounter++; + } else { + otherCounter++; } } @@ -58,6 +60,7 @@ public void reset() { altCounter = 0; orCounter = 0; andCounter = 0; + otherCounter = 0; } @Override @@ -66,6 +69,7 @@ public Result> getResult() { countedGroups.put("AlternativeGroup", altCounter); countedGroups.put("OrGroup", orCounter); countedGroups.put("AndGroup", andCounter); + countedGroups.put("OtherGroup", otherCounter); return Result.of(countedGroups); } } From 757a1a2caa8ac7f341c13b712328260cc6ab2db3 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 14:07:23 +0200 Subject: [PATCH 030/257] refactor: now uses Double instead of Float as per supervisor request --- .../model/analysis/SimpleTreeProperties.java | 13 ++++++------- .../analysis/visitor/TreeAvgChildrenCounter.java | 8 ++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index b93916e0..b570a33c 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -14,17 +14,16 @@ public class SimpleTreeProperties { /** * @param tree: feature tree - * @return number of features directly below the root of this subtree. + * {@return number of features directly below the root of this subtree.} */ public Result topFeatures(IFeatureTree tree) { - // int childrenCount = tree.getRoot().getChildrenCount(); // if we should find a subtree's root automatically - int childrenCount = tree.getChildrenCount(); + int childrenCount = tree.getChildrenCount(); // when doing computations: input the feature model return Result.of(childrenCount); } /** * @param tree: feature tree - * @return the number of features that have no child features + * {@return the number of features that have no child features} */ public Result leafFeaturesCounter(IFeatureTree tree) { return Trees.traverse(tree, new TreeLeafCounter()); @@ -32,7 +31,7 @@ public Result leafFeaturesCounter(IFeatureTree tree) { /** * @param tree: feature tree - * @return tree depth, meaning the longest path from this subtree's root to its most distant leaf node + * {@return tree depth, meaning the longest path from this subtree's root to its most distant leaf node} */ public Result treeDepth(IFeatureTree tree) { return Trees.traverse(tree, new TreeDepthCounter()); @@ -40,9 +39,9 @@ public Result treeDepth(IFeatureTree tree) { /** * @param tree: feature tree - * @return average number of children that each node in the tree has, rounded to integer. + * {@return average number of children that each node in the tree has, rounded to integer.} */ - public Result avgNumberOfChildren(IFeatureTree tree) { + public Result avgNumberOfChildren(IFeatureTree tree) { return Trees.traverse(tree, new TreeAvgChildrenCounter()); } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index 6943042a..0b716b4a 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -31,7 +31,7 @@ * * @author Sebastian Krieter */ -public class TreeAvgChildrenCounter implements ITreeVisitor, Float> { +public class TreeAvgChildrenCounter implements ITreeVisitor, Double> { private int nodeCount = 0; private int childCount = 0; @@ -50,10 +50,10 @@ public void reset() { } @Override - public Result getResult() { - float result = 0; + public Result getResult() { + double result = 0; if (nodeCount > 0) { - result = (float) childCount / nodeCount; + result = (double) childCount / nodeCount; } return Result.of(result); } From a3892d2718b0733e232f23a74069a45b17b23fb8 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 14:18:23 +0200 Subject: [PATCH 031/257] refactor: now uses Double instead of Float as per supervisor request --- .../de/featjar/feature/model/analysis/SimpleTreeProperties.java | 1 - .../feature/model/analysis/SimpleTreePropertiesTest.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index b570a33c..3452632c 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -11,7 +11,6 @@ import java.util.HashMap; public class SimpleTreeProperties { - /** * @param tree: feature tree * {@return number of features directly below the root of this subtree.} diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index dbad012b..7764a559 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -147,7 +147,7 @@ void testTreeDepth() { @Test void testAvgNumberOfChildren() { - float average; + double average; average = simpleTreeProperties.avgNumberOfChildren(minimalTree).get(); assertEquals(0.0, average); From 902fd9fbf01be98842a38ed578751baae616985c Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Mon, 6 Oct 2025 14:24:27 +0200 Subject: [PATCH 032/257] test: split test into visitor and computation tests --- .../feature/model/analysis/AtomsCount.java | 2 +- .../model/analysis/OperatorDistribution.java | 5 +- .../model/computation/ComputeAtomsCount.java | 7 +- .../ConstraintPropertiesVisitorTest.java | 189 ++++++++++++++++++ .../computation/ConstraintPropertiesTest.java | 14 +- 5 files changed, 202 insertions(+), 15 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java diff --git a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java index cf37c9b0..adb981c9 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java +++ b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java @@ -44,7 +44,7 @@ public class AtomsCount implements ITreeVisitor, Integer> { private boolean countBoolean = true; /** - * + * * @param countVariables decide if Atoms of type variable should be counted * @param countConstants decide if Atoms of type constants should be counted * @param countBoolean decide if Atoms of type True or False should be counted diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java index 8f97caeb..bc1dceff 100644 --- a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -25,7 +25,6 @@ import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.formula.structure.connective.IConnective; import de.featjar.formula.structure.connective.Reference; - import java.util.HashMap; import java.util.List; @@ -37,13 +36,13 @@ * @author Florian Beese */ public class OperatorDistribution implements ITreeVisitor, HashMap> { - // Saves the count of each operator, where each key is the name of the class of the operator + // Saves the count of each operator, where each key is the name of the class of the operator HashMap operatorCount = new HashMap(); @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); - if (node instanceof IConnective && ! (node instanceof Reference)) { + if (node instanceof IConnective && !(node instanceof Reference)) { String nodeKey = node.getClass().getSimpleName(); if (!operatorCount.containsKey(nodeKey)) { operatorCount.put(nodeKey, 1); diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index 44bf884f..db0c4d57 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -45,14 +45,13 @@ * */ public class ComputeAtomsCount extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); - //COUNTCONSTANTS decide if Atoms of type constants should be counted + // COUNTCONSTANTS decide if Atoms of type constants should be counted protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); - //COUNTVARIABLES decide if Atoms of type variable should be counted + // COUNTVARIABLES decide if Atoms of type variable should be counted protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); - //COUNTBOOLEAN decide if Atoms of type True or False should be counted + // COUNTBOOLEAN decide if Atoms of type True or False should be counted protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); - public ComputeAtomsCount(IComputation featureModel) { super( featureModel, diff --git a/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java new file mode 100644 index 00000000..274adbc7 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java @@ -0,0 +1,189 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import static de.featjar.formula.structure.Expressions.constant; +import static de.featjar.formula.structure.Expressions.integerAdd; +import static de.featjar.formula.structure.Expressions.variable; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import de.featjar.base.tree.Trees; +import de.featjar.base.tree.structure.ITree; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IConstraint; +import de.featjar.formula.structure.IExpression; +import de.featjar.formula.structure.IFormula; +import de.featjar.formula.structure.connective.And; +import de.featjar.formula.structure.connective.Implies; +import de.featjar.formula.structure.connective.Not; +import de.featjar.formula.structure.connective.Or; +import de.featjar.formula.structure.predicate.Equals; +import de.featjar.formula.structure.predicate.Literal; +import de.featjar.formula.structure.predicate.True; +import de.featjar.formula.structure.term.ITerm; +import de.featjar.formula.structure.term.value.Constant; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.Set; +import org.junit.jupiter.api.Test; + +public class ConstraintPropertiesVisitorTest { + + @Test + public void atomsVisitorTest() { + FeatureModel featureModel = createFeatureModel(); + Iterator constraintsIterator = + featureModel.getConstraints().iterator(); + IFormula formula1 = constraintsIterator.next().getFormula(); + IFormula formula2 = constraintsIterator.next().getFormula(); + IFormula formula3 = constraintsIterator.next().getFormula(); + int formula1BooleansCount = + Trees.traverse(formula1, new AtomsCount(false, false, true)).orElseThrow(); + int formula2ConstantsCount = + Trees.traverse(formula3, new AtomsCount(false, true, false)).orElseThrow(); + int formula3VariablesCount = + Trees.traverse(formula3, new AtomsCount(true, false, false)).orElseThrow(); + assertEquals(1, formula1BooleansCount); + assertEquals(3, formula2ConstantsCount); + assertEquals(1, formula3VariablesCount); + assertEquals(0, Trees.traverse(new And(), new AtomsCount(true, true, true)).orElseThrow()); + } + + @Test + public void featureDensityVisitorTest() { + FeatureModel featureModel = createFeatureModel(); + Iterator constraintsIterator = + featureModel.getConstraints().iterator(); + Set formula1Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensity()) + .orElseThrow(); + Set formula2Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensity()) + .orElseThrow(); + Set formula3Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensity()) + .orElseThrow(); + + assertEquals(5, formula1Features.size()); + assertTrue(formula1Features.contains("a")); + assertTrue(formula1Features.contains("b")); + assertTrue(formula1Features.contains("i")); + assertTrue(formula1Features.contains("k")); + assertTrue(formula1Features.contains("o")); + assertFalse(formula1Features.contains("x")); + assertFalse(formula1Features.contains("c")); + assertEquals(5, formula2Features.size()); + assertTrue(formula2Features.contains("a")); + assertTrue(formula2Features.contains("b")); + assertTrue(formula2Features.contains("i")); + assertTrue(formula2Features.contains("k")); + assertTrue(formula2Features.contains("o")); + assertFalse(formula2Features.contains("x")); + assertFalse(formula2Features.contains("c")); + assertEquals(1, formula3Features.size()); + assertTrue(formula3Features.contains("a")); + assertFalse(formula3Features.contains("b")); + assertEquals(0, Trees.traverse(new And(), new FeatureDensity()).orElseThrow().size()); + } + + @Test + public void operatorDensityVisitorTest() { + FeatureModel featureModel = createFeatureModel(); + Iterator constraintsIterator = + featureModel.getConstraints().iterator(); + HashMap formula1Count = Trees.traverse( + constraintsIterator.next().getFormula(), new OperatorDistribution()) + .orElseThrow(); + HashMap formula2Count = Trees.traverse( + constraintsIterator.next().getFormula(), new OperatorDistribution()) + .orElseThrow(); + HashMap formula3Count = Trees.traverse( + constraintsIterator.next().getFormula(), new OperatorDistribution()) + .orElseThrow(); + + assertEquals(2, formula1Count.get("And")); + assertEquals(1, formula1Count.get("Or")); + assertEquals(1, formula1Count.get("Not")); + assertEquals(1, formula1Count.get("Implies")); + assertEquals(4, formula1Count.size()); + + assertEquals(2, formula2Count.get("And")); + assertEquals(2, formula2Count.get("Or")); + assertEquals(1, formula2Count.get("Not")); + assertEquals(2, formula2Count.get("Implies")); + assertEquals(4, formula2Count.size()); + + assertEquals(0, formula3Count.size()); + assertEquals(0, Trees.traverse(null, new OperatorDistribution()).orElseThrow().size()); + } + + public FeatureModel createFeatureModel() { + FeatureModel featureModel = new FeatureModel(); + + // add Features (7) + featureModel.addFeature("a"); + featureModel.addFeature("b"); + featureModel.addFeature("c"); + featureModel.addFeature("i"); + featureModel.addFeature("k"); + featureModel.addFeature("o"); + featureModel.addFeature("x"); + + // define Features as literals + Literal literalA = new Literal("a"); + Literal literalB = new Literal("b"); + Literal literalC = new Literal("c"); + Literal literalI = new Literal("i"); + Literal literalK = new Literal("k"); + Literal literalO = new Literal("o"); + Literal literalX = new Literal("x"); + + // set some variables or literals + literalO.setPositive(true); + literalB.setPositive(false); + + // define terms + ITerm termAdd = integerAdd(constant(42L), variable("a", Long.class)); + ITerm termAddLiteral = integerAdd(constant(42L), new Constant(2L)); + // ITerm termAddLiteral1 = integerAdd(constant(42L), new Constant(literalA, literalA.getClass())); + + // operator((and,4), (or, 3), (not, 2), (implies, 3)) + // define formulas + // 8 literal, 5 operator((and,2), (or, 1), (not, 1), (implies, 1)), Features(a,b,i,k,o) + IFormula formula1 = new And( + literalA, + new Or(literalA, literalB, literalI), + new Not(literalB), + new Implies(literalK, literalO), + new And(literalB), + True.INSTANCE); + + IFormula formula2 = new Or(new Implies(formula1, literalO)); + IFormula formula3 = new Equals(termAdd, termAddLiteral); + + // add full formulas as constraints + featureModel.addConstraint(formula1); + featureModel.addConstraint(formula2); + featureModel.addConstraint(formula3); + return featureModel; + } +} diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index 854a7aed..90cd381f 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -27,7 +27,9 @@ import de.featjar.base.computation.Computations; import de.featjar.base.computation.IComputation; +import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.analysis.AtomsCount; import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.And; import de.featjar.formula.structure.connective.Implies; @@ -48,6 +50,8 @@ public void atomsTest() { FeatureModel featureModel = createFeatureModel(); IComputation compuational = Computations.of(featureModel).map(ComputeAtomsCount::new); + Trees.traverse( + featureModel.getConstraints().iterator().next().getFormula(), new AtomsCount(false, false, false)); assertEquals(23, compuational.compute()); assertEquals( 3, @@ -76,7 +80,7 @@ public void featureDensityTest() { FeatureModel featureModel = createFeatureModel(); float computational = Computations.of(featureModel).map(ComputeFeatureDensity::new).compute(); - assertEquals((float) 6 / (float) 7, computational); + assertEquals((float) 5 / (float) 7, computational); } // operator((and,4), (or, 3), (not, 2), (implies, 3)) @Test @@ -125,13 +129,11 @@ public FeatureModel createFeatureModel() { literalB.setPositive(false); // define terms - ITerm termAdd = integerAdd(constant(42L), variable("varA", Long.class)); + ITerm termAdd = integerAdd(constant(42L), variable("a", Long.class)); ITerm termAddLiteral = integerAdd(constant(42L), new Constant(2L)); // ITerm termAddLiteral1 = integerAdd(constant(42L), new Constant(literalA, literalA.getClass())); - // operator((and,4), (or, 3), (not, 2), (implies, 3)) // define formulas - // 8 literal, 5 operator((and,2), (or, 1), (not, 1), (implies, 1)), Features(a,b,i,k,o) IFormula formula1 = new And( literalA, new Or(literalA, literalB, literalI), @@ -139,12 +141,10 @@ public FeatureModel createFeatureModel() { new Implies(literalK, literalO), new And(literalB), True.INSTANCE); - // 9 literal, 7 operator((and,2), (or, 2), (not, 1), (implies, 2)), Features(a,b,i,k,o) + IFormula formula2 = new Or(new Implies(formula1, literalO)); - // 1 literal, 0 operator, 3 constants IFormula formula3 = new Equals(termAdd, termAddLiteral); - // add full formulas as constraints featureModel.addConstraint(formula1); featureModel.addConstraint(formula2); featureModel.addConstraint(formula3); From 38b324dc082c24ad27cf23f30c7579edd7fb4a65 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Mon, 6 Oct 2025 14:29:48 +0200 Subject: [PATCH 033/257] feat: concluded implementation of functionality --- .../feature/model/cli/PrintStatistics.java | 82 ++++++-------- .../model/cli/PrintStatisticsTest.java | 107 ++++++++++++++++++ 2 files changed, 143 insertions(+), 46 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index f0d0d3a6..3b479857 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -28,7 +28,6 @@ import de.featjar.base.io.IO; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.io.FeatureModelFormats; -import java.io.IOException; import java.nio.file.Path; import java.util.HashMap; import java.util.Map; @@ -41,24 +40,15 @@ */ public class PrintStatistics extends ACommand { - enum FileTypes { - XML, - CSV, - YAML, - JSON, - TXT - } - enum AnalysesScope { ALL, TREE_RELATED, CONSTRAINT_RELATED } - // options as command line arguments - public static final Option FILE_TYPE = - Option.newEnumOption("type", FileTypes.class).setDescription("Specifies file type"); + private int exit_status = 0; + // options as command line arguments public static final Option ANALYSES_SCOPE = Option.newEnumOption("scope", AnalysesScope.class).setDescription("Specifies scope of statistics"); @@ -70,69 +60,67 @@ enum AnalysesScope { @Override public int run(OptionList optionParser) { - // -----------------INPUT-------------------------------------------------- + if (!optionParser.getResult(INPUT_OPTION).isPresent()) { + FeatJAR.log().error("No Input file attached"); + return 1; + } + // opening input model Path path = optionParser.getResult(INPUT_OPTION).orElseThrow(); Result load = IO.load(path, FeatureModelFormats.getInstance()); IFeatureModel model = load.orElseThrow(); - // -----------------COLLECTING STATS-------------------------------------- - + // collecting statistics of the model, checking if scope is specified if (optionParser.getResult(ANALYSES_SCOPE).isPresent()) { data = collectStats(model, optionParser.get(ANALYSES_SCOPE)); } else { data = collectStats(model, AnalysesScope.ALL); } - // -----------------WRITING TO FILE--------------------------------------- - - // output path & file type specified - if (optionParser.getResult(OUTPUT_OPTION).isPresent() - && optionParser.getResult(FILE_TYPE).isPresent()) { - - try { - writeTo( - optionParser.getResult(OUTPUT_OPTION).get(), - optionParser.getResult(FILE_TYPE).get()); - } catch (IOException e) { - FeatJAR.log().error(e); - } - - // output path specified, but no file type - } else if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { - - FeatJAR.log().warning("Output path provided, but no file type specified."); + // if output path is specified, write statistics to file + if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { + Path outputPath = optionParser.get(OUTPUT_OPTION); + String fileExtension = IO.getFileExtension(outputPath); + writeTo(optionParser.getResult(OUTPUT_OPTION).get(), fileExtension); } - // ----------------PRINTING IN CONSOLE------------------------------------ - + // printing statistics to console if (optionParser.get(PRETTY_PRINT)) { printStatsPretty(); } else { printStats(); } - return 0; + return exit_status; } - // temporary for format type output - private void writeTo(Path path, FileTypes type) throws IOException { + private void writeTo(Path path, String type) { + switch (type) { - case XML: + case "xml": // TODO future Story Card: Write to XML - // Example: IO.save(new (), path, new XMLFeatureModelFormat()); + // IO.save(new Object(data), path, new XMLFeatureModelFormat()); + // IO.sa break; - case CSV: + case "csv": // TODO future Story Card: Write to CSV break; - case YAML: + case "yaml": // TODO future Story Card: Write to YAML break; - case JSON: + case "json": // TODO future Story Card: Write to JSON break; - case TXT: + case "txt": // TODO future Story Card: Write to TXT + break; + case "": + FeatJAR.log().error("Output file does not include file type."); + exit_status = 1; + break; + default: + FeatJAR.log().error("File type not valid: " + type); + exit_status = 1; } } @@ -142,9 +130,11 @@ private HashMap collectStats(IFeatureModel model, AnalysesScope if (scope == AnalysesScope.ALL || scope == AnalysesScope.CONSTRAINT_RELATED) { - // For Example model.getConstraintInfo() + // For Example data.put(model.getConstraintInfo()) + + } - } else if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { + if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { // For Example model.getTreeDepth() diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java new file mode 100644 index 00000000..0af4d877 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -0,0 +1,107 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.cli; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import de.featjar.base.FeatJAR; +import de.featjar.base.data.identifier.AIdentifier; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link AIdentifier} and {@link IIdentifiable}. + * + * @author Knut & Kilian + */ +public class PrintStatisticsTest { + + PrintStatistics printStats = new PrintStatistics(); + + @Test + void inputTest() throws IOException { + + int exit_code = FeatJAR.runTest( + "printStats", "--input", "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"); + assertEquals(0, exit_code); + } + + @Test + void noInput() throws IOException { + + assertEquals(1, FeatJAR.runTest("printStats", "--input")); + assertEquals(1, FeatJAR.runTest("printStats")); + } + + @Test + void outputWithFileValidExtension() throws IOException { + + int exit_code = FeatJAR.runTest( + "printStats", + "--input", + "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", + "--output", + "desktop/folder/model.xml"); + assertEquals(0, exit_code); + } + + @Test + void outputWithFileInvalidExtension() throws IOException { + + int exit_code = FeatJAR.runTest( + "printStats", + "--input", + "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", + "--output", + "desktop/folder/model.pdf"); + assertEquals(1, exit_code); + } + + @Test + void outputWithoutFileExtension() throws IOException { + + int exit_code = FeatJAR.runTest( + "printStats", + "--input", + "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", + "--output", + "desktop/folder"); + assertEquals(1, exit_code); + } + + @Test + void printPretty() throws IOException {} + + @Test + void printDefault() throws IOException {} + + @Test + void scopeAll() throws IOException {} + + @Test + void scopeTreeRelated() throws IOException {} + + @Test + void scopeConstraintRelated() throws IOException {} + + @Test + void scopeNotSpecified() throws IOException {} +} From 43830affb0a524c508ef3ba4a9edced90a139edf Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 14:38:12 +0200 Subject: [PATCH 034/257] refactor: now uses Double instead of Float as per supervisor request --- .../java/de/featjar/feature/model/io/csv/CSVExporter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java index 7c823806..57a0a1da 100644 --- a/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java +++ b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java @@ -44,7 +44,7 @@ private String roundAndCastToString (int numerator, int denominator) { return String.format("%.2f", (float) numerator / denominator); } - private String roundAndCastToString (float number) { + private String roundAndCastToString (double number) { return String.format("%.2f", number); } @@ -61,7 +61,7 @@ public LinkedHashMap gatherStatistics (IFeatureTree tree) { int andGroups = groupDistribution.get("AndGroup"); int allGroups = alternativeGroups + orGroups + andGroups; - float avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); + double avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); // linked map to preserve order for now, not sure if needed // todo: decide whether to make this a variable map, or a ready-to-write map From dc326abf6e2733edafb66c16163dd01f26850165 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 15:01:27 +0200 Subject: [PATCH 035/257] refactor: switched all calculations to the Computations wrapper. --- ...ComputeFeatureAverageNumberOfChildren.java | 50 ++++++++ .../ComputeFeatureFeaturesCounter.java | 50 ++++++++ .../ComputeFeatureGroupDistribution.java | 51 ++++++++ .../analysis/ComputeFeatureTopFeatures.java | 48 ++++++++ .../analysis/ComputeFeatureTreeDepth.java | 50 ++++++++ .../model/analysis/SimpleTreeProperties.java | 55 --------- .../feature/model/io/csv/CSVExporter.java | 113 ------------------ .../analysis/SimpleTreePropertiesTest.java | 53 +++++--- 8 files changed, 286 insertions(+), 184 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java delete mode 100644 src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java delete mode 100644 src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java new file mode 100644 index 00000000..4b4ff5ae --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; +import java.util.List; + +/** + * Calculates the number of features that have no child features. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureAverageNumberOfChildren extends AComputation { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureAverageNumberOfChildren(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Trees.traverse(tree, new TreeAvgChildrenCounter()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java new file mode 100644 index 00000000..12b466e8 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; +import java.util.List; + +/** + * Calculates the number of features that have no child features. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureFeaturesCounter extends AComputation { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureFeaturesCounter(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Trees.traverse(tree, new TreeLeafCounter()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java new file mode 100644 index 00000000..3370b175 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; +import java.util.HashMap; +import java.util.List; + +/** + * Calculates the number of features that have no child features. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureGroupDistribution extends AComputation> { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureGroupDistribution(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result> compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Trees.traverse(tree, new FeatureTreeGroupCounter()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java new file mode 100644 index 00000000..348e6148 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.feature.model.IFeatureTree; +import java.util.List; + +/** + * Calculates the number of features directly below the root of this subtree. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureTopFeatures extends AComputation { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureTopFeatures(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Result.of(tree.getChildrenCount()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java new file mode 100644 index 00000000..6dee4b48 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.base.tree.visitor.TreeDepthCounter; +import de.featjar.feature.model.IFeatureTree; +import java.util.List; + +/** + * Calculates the number of features that have no child features. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureTreeDepth extends AComputation { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureTreeDepth(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Trees.traverse(tree, new TreeDepthCounter()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java deleted file mode 100644 index 3452632c..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ /dev/null @@ -1,55 +0,0 @@ -package de.featjar.feature.model.analysis; - -import de.featjar.base.data.Result; -import de.featjar.base.tree.Trees; -import de.featjar.base.tree.visitor.TreeDepthCounter; -import de.featjar.feature.model.*; -import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; -import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; -import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; - -import java.util.HashMap; - -public class SimpleTreeProperties { - /** - * @param tree: feature tree - * {@return number of features directly below the root of this subtree.} - */ - public Result topFeatures(IFeatureTree tree) { - int childrenCount = tree.getChildrenCount(); // when doing computations: input the feature model - return Result.of(childrenCount); - } - - /** - * @param tree: feature tree - * {@return the number of features that have no child features} - */ - public Result leafFeaturesCounter(IFeatureTree tree) { - return Trees.traverse(tree, new TreeLeafCounter()); - } - - /** - * @param tree: feature tree - * {@return tree depth, meaning the longest path from this subtree's root to its most distant leaf node} - */ - public Result treeDepth(IFeatureTree tree) { - return Trees.traverse(tree, new TreeDepthCounter()); - } - - /** - * @param tree: feature tree - * {@return average number of children that each node in the tree has, rounded to integer.} - */ - public Result avgNumberOfChildren(IFeatureTree tree) { - return Trees.traverse(tree, new TreeAvgChildrenCounter()); - } - - /** Counts the number of different groups in this tree. - * @param tree: feature tree - * @return hashmap with the String keys "AlternativeGroup", "OrGroup" and "AndGroup" to get the respective counts - */ - public Result> groupDistribution(IFeatureTree tree) { - return Trees.traverse(tree, new FeatureTreeGroupCounter()); - } - -} diff --git a/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java deleted file mode 100644 index 57a0a1da..00000000 --- a/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java +++ /dev/null @@ -1,113 +0,0 @@ -package de.featjar.feature.model.io.csv; - -import de.featjar.base.data.identifier.Identifiers; -import de.featjar.base.io.IO; -import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.IFeature; -import de.featjar.feature.model.IFeatureTree; -import de.featjar.feature.model.analysis.SimpleTreeProperties; -import java.nio.file.Path; -import java.nio.file.Paths; - -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.stream.Collectors; - - -/** - * This is a temp class; the real implementation will be done via IFormat implementation. It will be deleted later. - */ -public class CSVExporter { - // should probably pull this from the IO Exporter / Importer - public final String DELIMITER = ";"; - public final Charset DEFAULT_CHARSET = IO.DEFAULT_CHARSET; - - public IFeatureTree makeTree () { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree rootTree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - rootTree.mutate().toAlternativeGroup(); - - IFeature childFeature1 = featureModel.mutate().addFeature("Root's Child (in AltGroup)"); - IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - IFeature childFeature2 = featureModel.mutate().addFeature("1st Child of Root's Child"); - childTree1.mutate().addFeatureBelow(childFeature2); - IFeature childFeature3 = featureModel.mutate().addFeature("2nd Child of Root's Child"); - childTree1.mutate().addFeatureBelow(childFeature3); - - return rootTree; - } - - // change formatting here - private String roundAndCastToString (int numerator, int denominator) { - return String.format("%.2f", (float) numerator / denominator); - } - - private String roundAndCastToString (double number) { - return String.format("%.2f", number); - } - - public LinkedHashMap gatherStatistics (IFeatureTree tree) { - SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); - - int topFeatures = simpleTreeProperties.topFeatures(tree).get(); - int leafFeatures = simpleTreeProperties.leafFeaturesCounter(tree).get(); - int treeDepth = simpleTreeProperties.treeDepth(tree).get(); - - HashMap groupDistribution = simpleTreeProperties.groupDistribution(tree).get(); - int alternativeGroups = groupDistribution.get("AlternativeGroup"); - int orGroups = groupDistribution.get("OrGroup"); - int andGroups = groupDistribution.get("AndGroup"); - int allGroups = alternativeGroups + orGroups + andGroups; - - double avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); - - // linked map to preserve order for now, not sure if needed - // todo: decide whether to make this a variable map, or a ready-to-write map - LinkedHashMap map = new LinkedHashMap<>(); - map.put("Features Directly Below Root", topFeatures); - map.put("Features That Have No Child Features", leafFeatures); - map.put("Tree Depth", treeDepth); - map.put("Share of Alternative Groups", roundAndCastToString(alternativeGroups, allGroups)); - map.put("Share of Or-Groups", roundAndCastToString(orGroups, allGroups)); - map.put("Share of And-Groups", roundAndCastToString(andGroups, allGroups)); - map.put("Average Number of Children", roundAndCastToString(avgNumberOfChildren)); - - return map; - } - - public void export(String[] csvStrings) { - Path path = Paths.get("C:\\Users\\bentu\\Desktop\\myfile.csv"); - - /* - IO.write( - String.join("\n", csvStrings), - path, - DEFAULT_CHARSET - ); - - */ - - } - - public static void main(String[] args){ - CSVExporter csvExporter = new CSVExporter(); - IFeatureTree tree = csvExporter.makeTree(); - LinkedHashMap stats = csvExporter.gatherStatistics(tree); - // outputs the stat titles in order - String firstLine = String.join(csvExporter.DELIMITER, stats.keySet()); - - // outputs the stat values in order - String secondLine = stats.values().stream() - .map(String::valueOf) // safely converts all objects to String, including nulls - .collect(Collectors.joining(csvExporter.DELIMITER)); - - System.out.println(firstLine); // prints the column names - System.out.println(secondLine); // prints the stats (for the first tree) - - // todo iformat, xmlformat - - } - -} diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 7764a559..39e9814e 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; import de.featjar.Common; +import de.featjar.base.computation.Computations; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeature; @@ -31,7 +32,6 @@ import org.junit.jupiter.api.Test; public class SimpleTreePropertiesTest extends Common { - SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); IFeatureTree minimalTree = generateMinimalTree(); IFeatureTree smallTree = generateSmallTree(); IFeatureTree mediumTree = generateMediumTree(); @@ -107,13 +107,16 @@ private IFeatureTree generateMediumTree() { void testTopFeatures() { int rootChildren; - rootChildren = simpleTreeProperties.topFeatures(minimalTree).get(); + rootChildren = + Computations.of(minimalTree).map(ComputeFeatureTopFeatures::new).compute(); assertEquals(0, rootChildren); - rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); + rootChildren = + Computations.of(smallTree).map(ComputeFeatureTopFeatures::new).compute(); assertEquals(1, rootChildren); - rootChildren = simpleTreeProperties.topFeatures(mediumTree).get(); + rootChildren = + Computations.of(mediumTree).map(ComputeFeatureTopFeatures::new).compute(); assertEquals(3, rootChildren); } @@ -121,13 +124,19 @@ void testTopFeatures() { void testLeafFeaturesCounter() { int leaves; - leaves = simpleTreeProperties.leafFeaturesCounter(minimalTree).get(); + leaves = Computations.of(minimalTree) + .map(ComputeFeatureFeaturesCounter::new) + .compute(); assertEquals(1, leaves); - leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); + leaves = Computations.of(smallTree) + .map(ComputeFeatureFeaturesCounter::new) + .compute(); assertEquals(2, leaves); - leaves = simpleTreeProperties.leafFeaturesCounter(mediumTree).get(); + leaves = Computations.of(mediumTree) + .map(ComputeFeatureFeaturesCounter::new) + .compute(); assertEquals(6, leaves); } @@ -135,13 +144,13 @@ void testLeafFeaturesCounter() { void testTreeDepth() { int depth; - depth = simpleTreeProperties.treeDepth(minimalTree).get(); + depth = Computations.of(minimalTree).map(ComputeFeatureTreeDepth::new).compute(); assertEquals(1, depth); - depth = simpleTreeProperties.treeDepth(smallTree).get(); + depth = Computations.of(smallTree).map(ComputeFeatureTreeDepth::new).compute(); assertEquals(3, depth); - depth = simpleTreeProperties.treeDepth(mediumTree).get(); + depth = Computations.of(mediumTree).map(ComputeFeatureTreeDepth::new).compute(); assertEquals(3, depth); } @@ -149,13 +158,19 @@ void testTreeDepth() { void testAvgNumberOfChildren() { double average; - average = simpleTreeProperties.avgNumberOfChildren(minimalTree).get(); + average = Computations.of(minimalTree) + .map(ComputeFeatureAverageNumberOfChildren::new) + .compute(); assertEquals(0.0, average); - average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); + average = Computations.of(smallTree) + .map(ComputeFeatureAverageNumberOfChildren::new) + .compute(); assertEquals(0.75, average); - average = simpleTreeProperties.avgNumberOfChildren(mediumTree).get(); + average = Computations.of(mediumTree) + .map(ComputeFeatureAverageNumberOfChildren::new) + .compute(); assertTrue(0.888 < average && average < 0.889); } @@ -163,17 +178,23 @@ void testAvgNumberOfChildren() { void testGroupDistribution() { HashMap groupCounts; - groupCounts = simpleTreeProperties.groupDistribution(minimalTree).get(); + groupCounts = Computations.of(minimalTree) + .map(ComputeFeatureGroupDistribution::new) + .compute(); assertEquals(0, groupCounts.get("AlternativeGroup")); assertEquals(1, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); - groupCounts = simpleTreeProperties.groupDistribution(smallTree).get(); + groupCounts = Computations.of(smallTree) + .map(ComputeFeatureGroupDistribution::new) + .compute(); assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(3, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); - groupCounts = simpleTreeProperties.groupDistribution(mediumTree).get(); + groupCounts = Computations.of(mediumTree) + .map(ComputeFeatureGroupDistribution::new) + .compute(); assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(7, groupCounts.get("AndGroup")); assertEquals(1, groupCounts.get("OrGroup")); From 24044cd311d463151fa4f18a8c24d91c40cbefb4 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 1 Oct 2025 12:50:22 +0200 Subject: [PATCH 036/257] sample file for folder structure --- src/main/java/de/featjar/feature/model/analysis/info.md | 1 + 1 file changed, 1 insertion(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/info.md diff --git a/src/main/java/de/featjar/feature/model/analysis/info.md b/src/main/java/de/featjar/feature/model/analysis/info.md new file mode 100644 index 00000000..16dc75b2 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/info.md @@ -0,0 +1 @@ +example file. Put your classes here. \ No newline at end of file From 7948ae92b0c8977c42e5cbd038ce5dedeb857525 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 1 Oct 2025 16:50:10 +0200 Subject: [PATCH 037/257] feat: added visitor to count atoms and its computation method --- .../model/analysis/ConstraintProperties.java | 50 +++++++++++++++++++ .../ConstraintPropertiesFeatureShare.java | 45 +++++++++++++++++ .../ComputeConstraintProperties.java | 42 ++++++++++++++++ 3 files changed, 137 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java create mode 100644 src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java b/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java new file mode 100644 index 00000000..0a3877b3 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.formula.structure.ATerminalExpression; +import java.util.List; + +public class ConstraintProperties implements ITreeVisitor, Integer> { + private int atomsCount = 0; + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + if (node instanceof ATerminalExpression) { + atomsCount++; + } + return TraversalAction.CONTINUE; + } + + @Override + public Result getResult() { + return Result.of(atomsCount); + } + + @Override + public void reset() { + atomsCount = 0; + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java b/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java new file mode 100644 index 00000000..10e3502a --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; +import java.util.List; + +public class ConstraintPropertiesFeatureShare implements ITreeVisitor, Integer> { + + @Override + public TraversalAction firstVisit(List> path) { + + return TraversalAction.CONTINUE; + } + + @Override + public Result getResult() { + return Result.of(0); + } + + @Override + public void reset() { + ; + } +} diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java b/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java new file mode 100644 index 00000000..c1be590a --- /dev/null +++ b/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import de.featjar.base.computation.*; +import de.featjar.base.computation.AComputation; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.analysis.ConstraintProperties; +import de.featjar.formula.structure.IFormula; +import java.util.List; + +public class ComputeConstraintProperties extends AComputation { + protected static final Dependency tree = Dependency.newDependency(IFormula.class); + + public ComputeConstraintProperties(IComputation iformula) { + super(iformula); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + return Trees.traverse(tree.get(dependencyList), new ConstraintProperties()); + } +} From 435d950a4952d967d8682f8d4da188c8744b4a2e Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 1 Oct 2025 16:51:05 +0200 Subject: [PATCH 038/257] chores: removed placeholder file --- src/main/java/de/featjar/feature/model/analysis/info.md | 1 - 1 file changed, 1 deletion(-) delete mode 100644 src/main/java/de/featjar/feature/model/analysis/info.md diff --git a/src/main/java/de/featjar/feature/model/analysis/info.md b/src/main/java/de/featjar/feature/model/analysis/info.md deleted file mode 100644 index 16dc75b2..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/info.md +++ /dev/null @@ -1 +0,0 @@ -example file. Put your classes here. \ No newline at end of file From 1662de5bf5f6a2f6f0b440fcb4bb606c4b4b8975 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 2 Oct 2025 10:45:29 +0200 Subject: [PATCH 039/257] feat: Changed from IFormula input to FeatureModel input in analysis and compuational classes feat: added options for counting different type of atoms test: Added a test for the computational class --- .../model/analysis/ConstraintProperties.java | 14 ++++- .../ComputeConstraintProperties.java | 29 +++++++--- .../computation/ConstraintPropertiesTest.java | 53 +++++++++++++++++++ 3 files changed, 88 insertions(+), 8 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java b/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java index 0a3877b3..55d431b1 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java @@ -23,16 +23,26 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; -import de.featjar.formula.structure.ATerminalExpression; +import de.featjar.formula.structure.term.value.Constant; +import de.featjar.formula.structure.term.value.Variable; import java.util.List; public class ConstraintProperties implements ITreeVisitor, Integer> { private int atomsCount = 0; + private boolean countVariables = true; + private boolean countConstants = true; + + public ConstraintProperties(boolean countVariables, boolean countConstants) { + this.countConstants = countConstants; + this.countVariables = countVariables; + } @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); - if (node instanceof ATerminalExpression) { + if (countConstants && node instanceof Constant) { + atomsCount++; + } else if (countVariables && node instanceof Variable) { atomsCount++; } return TraversalAction.CONTINUE; diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java b/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java index c1be590a..d49f8b67 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java @@ -21,22 +21,39 @@ package de.featjar.feature.model.computation; import de.featjar.base.computation.*; -import de.featjar.base.computation.AComputation; import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IConstraint; import de.featjar.feature.model.analysis.ConstraintProperties; -import de.featjar.formula.structure.IFormula; +import java.util.Collection; +import java.util.Iterator; import java.util.List; public class ComputeConstraintProperties extends AComputation { - protected static final Dependency tree = Dependency.newDependency(IFormula.class); + protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); - public ComputeConstraintProperties(IComputation iformula) { - super(iformula); + public ComputeConstraintProperties(IComputation featureModel) { + super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); } @Override public Result compute(List dependencyList, Progress progress) { - return Trees.traverse(tree.get(dependencyList), new ConstraintProperties()); + Collection Constraints = FEATUREMODEL.get(dependencyList).getConstraints(); + int atomsSum = 0; + + Iterator constraintIterator = Constraints.iterator(); + while (constraintIterator.hasNext()) { + atomsSum = atomsSum + + Trees.traverse( + constraintIterator.next().getFormula(), + new ConstraintProperties( + COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) + .orElse(0); + } + + return Result.of(atomsSum); } } diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java new file mode 100644 index 00000000..a545da15 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -0,0 +1,53 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import de.featjar.base.computation.*; +import de.featjar.feature.model.FeatureModel; +import de.featjar.formula.structure.IFormula; +import de.featjar.formula.structure.connective.*; +import de.featjar.formula.structure.predicate.Literal; +import org.junit.jupiter.api.Test; + +public class ConstraintPropertiesTest { + // FeatJAR.(); + // FeatureJAR.log(); + + @Test + public void AtomsTest() { + FeatureModel featureModel = new FeatureModel(); + Literal literal1 = new Literal("o"); + literal1.setPositive(true); + IFormula tree1 = new And( + new Literal("a"), new Literal("b"), new Literal("x"), new Or(literal1, new Literal(false, "i"))); + IFormula tree2 = new And(new Literal("a"), new Or(literal1, new Literal(false, "i"))); + featureModel.addConstraint(tree1); + featureModel.addConstraint(tree2); + int compuational = Computations.of(featureModel) + .map(ComputeConstraintProperties::new) + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .compute(); + tree1.print(); + assertEquals(0, compuational); + } +} From ae4d50ce49f2ababe6391de6db7d79b0666051e7 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 2 Oct 2025 15:06:35 +0200 Subject: [PATCH 040/257] feat: Added functionality for computing FeatureDensity, OperatorDistribution and AverageConstraintSize including simple tests --- .../model/analysis/AverageConstraint.java | 23 +++++++ ...sFeatureShare.java => FeatureDensity.java} | 18 +++-- .../model/analysis/OperatorDistribution.java | 56 ++++++++++++++++ .../computation/ComputeAverageConstraint.java | 65 ++++++++++++++++++ .../computation/ComputeFeatureDensity.java | 63 ++++++++++++++++++ .../ComputeOperatorDistribution.java | 66 +++++++++++++++++++ .../computation/ConstraintPropertiesTest.java | 59 ++++++++++++++--- 7 files changed, 337 insertions(+), 13 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java rename src/main/java/de/featjar/feature/model/analysis/{ConstraintPropertiesFeatureShare.java => FeatureDensity.java} (68%) create mode 100644 src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java create mode 100644 src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java create mode 100644 src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java create mode 100644 src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java diff --git a/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java b/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java new file mode 100644 index 00000000..1764da7a --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java @@ -0,0 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +public class AverageConstraint {} diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java similarity index 68% rename from src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java rename to src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java index 10e3502a..68786bfa 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ConstraintPropertiesFeatureShare.java +++ b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java @@ -23,23 +23,31 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.formula.structure.term.value.Variable; +import java.util.HashSet; import java.util.List; +import java.util.Set; -public class ConstraintPropertiesFeatureShare implements ITreeVisitor, Integer> { +public class FeatureDensity implements ITreeVisitor, Set> { + private Set containedFeatures; @Override public TraversalAction firstVisit(List> path) { - + final ITree node = ITreeVisitor.getCurrentNode(path); + if (node instanceof Variable) { + Variable nodeVar = (Variable) node; + containedFeatures.add(nodeVar.getName()); + } return TraversalAction.CONTINUE; } @Override - public Result getResult() { - return Result.of(0); + public Result> getResult() { + return Result.of(containedFeatures); } @Override public void reset() { - ; + containedFeatures = new HashSet(); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java new file mode 100644 index 00000000..446c86af --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.formula.structure.connective.IConnective; +import java.util.HashMap; +import java.util.List; + +public class OperatorDistribution implements ITreeVisitor, HashMap> { + HashMap operatorCountMap = new HashMap(); + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + if (node instanceof IConnective) { + String nodeKey = node.getClass().getSimpleName(); + if (!operatorCountMap.containsKey(nodeKey)) { + operatorCountMap.put(nodeKey, 1); + } else { + operatorCountMap.replace(nodeKey, operatorCountMap.get(nodeKey) + 1); + } + } + return TraversalAction.CONTINUE; + } + + @Override + public Result> getResult() { + return Result.of(operatorCountMap); + } + + @Override + public void reset() { + operatorCountMap.clear(); + } +} diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java new file mode 100644 index 00000000..171d4ca0 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -0,0 +1,65 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Computations; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IConstraint; +import de.featjar.feature.model.analysis.ConstraintProperties; +import java.util.Collection; +import java.util.Iterator; +import java.util.List; + +public class ComputeAverageConstraint extends AComputation { + protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + + public ComputeAverageConstraint(IComputation featureModel) { + super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + FeatureModel featureModel = FEATUREMODEL.get(dependencyList); + Collection Constraints = featureModel.getConstraints(); + int atomsSum = 0; + + Iterator constraintIterator = Constraints.iterator(); + while (constraintIterator.hasNext()) { + atomsSum = atomsSum + + Trees.traverse( + constraintIterator.next().getFormula(), + new ConstraintProperties( + COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) + .orElse(0); + } + System.out.println(atomsSum + " " + featureModel.getConstraints().size()); + return Result.of( + (float) atomsSum / (float) featureModel.getConstraints().size()); + } +} diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java new file mode 100644 index 00000000..b50d76cc --- /dev/null +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -0,0 +1,63 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IConstraint; +import de.featjar.feature.model.analysis.FeatureDensity; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Set; + +public class ComputeFeatureDensity extends AComputation { + protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + + public ComputeFeatureDensity(IComputation featureModel) { + super(featureModel); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + FeatureModel featureModel = FEATUREMODEL.get(dependencyList); + Collection Constraints = featureModel.getConstraints(); + Set unionSet = new HashSet(); + + Iterator constraintIterator = Constraints.iterator(); + while (constraintIterator.hasNext()) { + unionSet.addAll(Trees.traverse(constraintIterator.next().getFormula(), new FeatureDensity()) + .orElse(Collections.emptySet())); + } + + System.out.println( + "" + unionSet.size() + FEATUREMODEL.get(dependencyList).getNumberOfFeatures()); + return Result.of((float) unionSet.size() + / (float) FEATUREMODEL.get(dependencyList).getNumberOfFeatures()); + } +} diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java new file mode 100644 index 00000000..698a74d5 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.computation; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IConstraint; +import de.featjar.feature.model.analysis.OperatorDistribution; +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class ComputeOperatorDistribution extends AComputation> { + protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + + public ComputeOperatorDistribution(IComputation featureModel) { + super(featureModel); + } + + @Override + public Result> compute(List dependencyList, Progress progress) { + FeatureModel featureModel = FEATUREMODEL.get(dependencyList); + Collection Constraints = featureModel.getConstraints(); + HashMap operatorCountMap = new HashMap(); + Iterator constraintIterator = Constraints.iterator(); + + while (constraintIterator.hasNext()) { + HashMap currentOperatorCountMap = Trees.traverse( + constraintIterator.next().getFormula(), new OperatorDistribution()) + .orElse(new HashMap()); + currentOperatorCountMap.forEach((key, value) -> { + if (operatorCountMap.containsKey(key)) { + operatorCountMap.replace(key, operatorCountMap.get(key) + value); + } else { + operatorCountMap.put(key, value); + } + }); + } + + return Result.of(operatorCountMap); + } +} diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index a545da15..d236f302 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -27,6 +27,7 @@ import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.*; import de.featjar.formula.structure.predicate.Literal; +import java.util.HashMap; import org.junit.jupiter.api.Test; public class ConstraintPropertiesTest { @@ -34,20 +35,62 @@ public class ConstraintPropertiesTest { // FeatureJAR.log(); @Test - public void AtomsTest() { + public void atomsTest() { + FeatureModel featureModel = createFeatureModel(); + int compuational = Computations.of(featureModel) + .map(ComputeConstraintProperties::new) + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .compute(); + assertEquals(0, compuational); + } + + @Test + public void featureDensityTest() { + FeatureModel featureModel = createFeatureModel(); + float computational = + Computations.of(featureModel).map(ComputeFeatureDensity::new).compute(); + assertEquals((float) 5 / (float) 6, computational); + } + + @Test + public void operatorDensityTest() { + FeatureModel featureModel = createFeatureModel(); + HashMap computational = Computations.of(featureModel) + .map(ComputeOperatorDistribution::new) + .compute(); + System.out.println(computational); + assertEquals(3, computational.get("And")); + assertEquals(2, computational.get("Or")); + } + + @Test + public void AverageConstraint() { + FeatureModel featureModel = createFeatureModel(); + float computational = + Computations.of(featureModel).map(ComputeAverageConstraint::new).compute(); + System.out.println(computational); + assertEquals((float) 8 / (float) 2, computational); + } + + public FeatureModel createFeatureModel() { FeatureModel featureModel = new FeatureModel(); + featureModel.addFeature("o"); + featureModel.addFeature("b"); + featureModel.addFeature("x"); + featureModel.addFeature("a"); + featureModel.addFeature("i"); + featureModel.addFeature("k"); Literal literal1 = new Literal("o"); literal1.setPositive(true); IFormula tree1 = new And( - new Literal("a"), new Literal("b"), new Literal("x"), new Or(literal1, new Literal(false, "i"))); + new And(), + new Literal("a"), + new Literal("b"), + new Literal("x"), + new Or(literal1, new Literal(false, "i"))); IFormula tree2 = new And(new Literal("a"), new Or(literal1, new Literal(false, "i"))); featureModel.addConstraint(tree1); featureModel.addConstraint(tree2); - int compuational = Computations.of(featureModel) - .map(ComputeConstraintProperties::new) - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) - .compute(); - tree1.print(); - assertEquals(0, compuational); + return featureModel; } } From 57b301096019c79f576994d289b0d449cec18817 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 2 Oct 2025 16:53:49 +0200 Subject: [PATCH 041/257] test: reshaped the tests --- .../computation/ConstraintPropertiesTest.java | 100 +++++++++++++----- 1 file changed, 76 insertions(+), 24 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index d236f302..08f3dd55 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -20,13 +20,19 @@ */ package de.featjar.feature.model.computation; +import static de.featjar.formula.structure.Expressions.constant; +import static de.featjar.formula.structure.Expressions.integerAdd; +import static de.featjar.formula.structure.Expressions.variable; import static org.junit.jupiter.api.Assertions.assertEquals; import de.featjar.base.computation.*; import de.featjar.feature.model.FeatureModel; import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.*; +import de.featjar.formula.structure.predicate.Equals; import de.featjar.formula.structure.predicate.Literal; +import de.featjar.formula.structure.term.ITerm; +import de.featjar.formula.structure.term.value.Constant; import java.util.HashMap; import org.junit.jupiter.api.Test; @@ -37,11 +43,26 @@ public class ConstraintPropertiesTest { @Test public void atomsTest() { FeatureModel featureModel = createFeatureModel(); - int compuational = Computations.of(featureModel) - .map(ComputeConstraintProperties::new) - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) - .compute(); - assertEquals(0, compuational); + IComputation compuational = Computations.of(featureModel).map(ComputeConstraintProperties::new); + + assertEquals(21, compuational.compute()); + assertEquals( + 3, + compuational + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .compute()); + assertEquals( + 18, + compuational + .set(ComputeConstraintProperties.COUNTCONSTANTS, Boolean.FALSE) + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.TRUE) + .compute()); + assertEquals( + 0, + compuational + .set(ComputeConstraintProperties.COUNTCONSTANTS, Boolean.FALSE) + .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .compute()); } @Test @@ -49,9 +70,9 @@ public void featureDensityTest() { FeatureModel featureModel = createFeatureModel(); float computational = Computations.of(featureModel).map(ComputeFeatureDensity::new).compute(); - assertEquals((float) 5 / (float) 6, computational); + assertEquals((float) 6.0 / (float) 7.0, computational); } - + // operator((and,4), (or, 3), (not, 2), (implies, 3)) @Test public void operatorDensityTest() { FeatureModel featureModel = createFeatureModel(); @@ -59,8 +80,10 @@ public void operatorDensityTest() { .map(ComputeOperatorDistribution::new) .compute(); System.out.println(computational); - assertEquals(3, computational.get("And")); - assertEquals(2, computational.get("Or")); + assertEquals(4, computational.get("And")); + assertEquals(3, computational.get("Or")); + assertEquals(2, computational.get("Not")); + assertEquals(3, computational.get("Implies")); } @Test @@ -69,28 +92,57 @@ public void AverageConstraint() { float computational = Computations.of(featureModel).map(ComputeAverageConstraint::new).compute(); System.out.println(computational); - assertEquals((float) 8 / (float) 2, computational); + assertEquals(21.0 / 3.0, computational); } public FeatureModel createFeatureModel() { FeatureModel featureModel = new FeatureModel(); - featureModel.addFeature("o"); - featureModel.addFeature("b"); - featureModel.addFeature("x"); + + // add Features (7) featureModel.addFeature("a"); + featureModel.addFeature("b"); + featureModel.addFeature("c"); featureModel.addFeature("i"); featureModel.addFeature("k"); - Literal literal1 = new Literal("o"); - literal1.setPositive(true); - IFormula tree1 = new And( - new And(), - new Literal("a"), - new Literal("b"), - new Literal("x"), - new Or(literal1, new Literal(false, "i"))); - IFormula tree2 = new And(new Literal("a"), new Or(literal1, new Literal(false, "i"))); - featureModel.addConstraint(tree1); - featureModel.addConstraint(tree2); + featureModel.addFeature("o"); + featureModel.addFeature("x"); + + // define Features as literals + Literal literalA = new Literal("a"); + Literal literalB = new Literal("b"); + Literal literalC = new Literal("c"); + Literal literalI = new Literal("i"); + Literal literalK = new Literal("k"); + Literal literalO = new Literal("o"); + Literal literalX = new Literal("x"); + + // set some variables or literals + literalO.setPositive(true); + literalB.setPositive(false); + + // define terms + ITerm termAdd = integerAdd(constant(42L), variable("varA", Long.class)); + ITerm termAddLiteral = integerAdd(constant(42L), new Constant(2L)); + // ITerm termAddLiteral1 = integerAdd(constant(42L), new Constant(literalA, literalA.getClass())); + + // operator((and,4), (or, 3), (not, 2), (implies, 3)) + // define formulas + // 8 literal, 5 operator((and,2), (or, 1), (not, 1), (implies, 1)), Features(a,b,i,k,o) + IFormula formula1 = new And( + literalA, + new Or(literalA, literalB, literalI), + new Not(literalB), + new Implies(literalK, literalO), + new And(literalB)); + // 9 literal, 7 operator((and,2), (or, 2), (not, 1), (implies, 2)), Features(a,b,i,k,o) + IFormula formula2 = new Or(new Implies(formula1, literalO)); + // 1 literal, 0 operator, 3 constants + IFormula formula3 = new Equals(termAdd, termAddLiteral); + + // add full formulas as constraints + featureModel.addConstraint(formula1); + featureModel.addConstraint(formula2); + featureModel.addConstraint(formula3); return featureModel; } } From fe02b10df39deb8e48c8cbafaf8c6fcf36da11b1 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Sun, 5 Oct 2025 11:17:45 +0200 Subject: [PATCH 042/257] refactor: assigned more specific names to variables and adjusted imports --- ...straintProperties.java => AtomsCount.java} | 11 +++++++-- .../model/analysis/AverageConstraint.java | 23 ------------------- .../model/analysis/FeatureDensity.java | 6 +++++ .../model/analysis/OperatorDistribution.java | 18 ++++++++++----- ...Properties.java => ComputeAtomsCount.java} | 14 +++++++---- .../computation/ComputeAverageConstraint.java | 5 ++-- .../computation/ComputeFeatureDensity.java | 3 --- .../ComputeOperatorDistribution.java | 14 +++++------ .../computation/ConstraintPropertiesTest.java | 12 +++++----- 9 files changed, 51 insertions(+), 55 deletions(-) rename src/main/java/de/featjar/feature/model/analysis/{ConstraintProperties.java => AtomsCount.java} (84%) delete mode 100644 src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java rename src/main/java/de/featjar/feature/model/computation/{ComputeConstraintProperties.java => ComputeAtomsCount.java} (82%) diff --git a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java similarity index 84% rename from src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java rename to src/main/java/de/featjar/feature/model/analysis/AtomsCount.java index 55d431b1..31824ee1 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ConstraintProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java @@ -27,12 +27,19 @@ import de.featjar.formula.structure.term.value.Variable; import java.util.List; -public class ConstraintProperties implements ITreeVisitor, Integer> { +/** + * Counts the number of used variables and constants in a tree. + * By default, both are counted, but it can be set to count only one of the two. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ +public class AtomsCount implements ITreeVisitor, Integer> { private int atomsCount = 0; private boolean countVariables = true; private boolean countConstants = true; - public ConstraintProperties(boolean countVariables, boolean countConstants) { + public AtomsCount(boolean countVariables, boolean countConstants) { this.countConstants = countConstants; this.countVariables = countVariables; } diff --git a/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java b/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java deleted file mode 100644 index 1764da7a..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/AverageConstraint.java +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright (C) 2025 FeatJAR-Development-Team - * - * This file is part of FeatJAR-feature-model. - * - * feature-model is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3.0 of the License, - * or (at your option) any later version. - * - * feature-model is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with feature-model. If not, see . - * - * See for further information. - */ -package de.featjar.feature.model.analysis; - -public class AverageConstraint {} diff --git a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java index 68786bfa..fb7aeb40 100644 --- a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java @@ -28,6 +28,12 @@ import java.util.List; import java.util.Set; +/** + * Enumerates the names of all distinct variables occurring in a tree. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class FeatureDensity implements ITreeVisitor, Set> { private Set containedFeatures; diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java index 446c86af..6cd061f3 100644 --- a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -27,18 +27,24 @@ import java.util.HashMap; import java.util.List; +/** + * Counts the the absolute occurrence of different operators in a tree. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class OperatorDistribution implements ITreeVisitor, HashMap> { - HashMap operatorCountMap = new HashMap(); + HashMap operatorCount = new HashMap(); @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); if (node instanceof IConnective) { String nodeKey = node.getClass().getSimpleName(); - if (!operatorCountMap.containsKey(nodeKey)) { - operatorCountMap.put(nodeKey, 1); + if (!operatorCount.containsKey(nodeKey)) { + operatorCount.put(nodeKey, 1); } else { - operatorCountMap.replace(nodeKey, operatorCountMap.get(nodeKey) + 1); + operatorCount.replace(nodeKey, operatorCount.get(nodeKey) + 1); } } return TraversalAction.CONTINUE; @@ -46,11 +52,11 @@ public TraversalAction firstVisit(List> path) { @Override public Result> getResult() { - return Result.of(operatorCountMap); + return Result.of(operatorCount); } @Override public void reset() { - operatorCountMap.clear(); + operatorCount.clear(); } } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java similarity index 82% rename from src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java rename to src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index d49f8b67..35c6916f 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeConstraintProperties.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -20,22 +20,26 @@ */ package de.featjar.feature.model.computation; -import de.featjar.base.computation.*; +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Computations; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.feature.model.analysis.ConstraintProperties; +import de.featjar.feature.model.analysis.AtomsCount; import java.util.Collection; import java.util.Iterator; import java.util.List; -public class ComputeConstraintProperties extends AComputation { +public class ComputeAtomsCount extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); - public ComputeConstraintProperties(IComputation featureModel) { + public ComputeAtomsCount(IComputation featureModel) { super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); } @@ -49,7 +53,7 @@ public Result compute(List dependencyList, Progress progress) { atomsSum = atomsSum + Trees.traverse( constraintIterator.next().getFormula(), - new ConstraintProperties( + new AtomsCount( COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) .orElse(0); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 171d4ca0..4e9e4d82 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -29,7 +29,7 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.feature.model.analysis.ConstraintProperties; +import de.featjar.feature.model.analysis.AtomsCount; import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -54,11 +54,10 @@ public Result compute(List dependencyList, Progress progress) { atomsSum = atomsSum + Trees.traverse( constraintIterator.next().getFormula(), - new ConstraintProperties( + new AtomsCount( COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) .orElse(0); } - System.out.println(atomsSum + " " + featureModel.getConstraints().size()); return Result.of( (float) atomsSum / (float) featureModel.getConstraints().size()); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java index b50d76cc..ede39092 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -54,9 +54,6 @@ public Result compute(List dependencyList, Progress progress) { unionSet.addAll(Trees.traverse(constraintIterator.next().getFormula(), new FeatureDensity()) .orElse(Collections.emptySet())); } - - System.out.println( - "" + unionSet.size() + FEATUREMODEL.get(dependencyList).getNumberOfFeatures()); return Result.of((float) unionSet.size() / (float) FEATUREMODEL.get(dependencyList).getNumberOfFeatures()); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java index 698a74d5..499031c6 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -45,22 +45,22 @@ public ComputeOperatorDistribution(IComputation featureModel) { public Result> compute(List dependencyList, Progress progress) { FeatureModel featureModel = FEATUREMODEL.get(dependencyList); Collection Constraints = featureModel.getConstraints(); - HashMap operatorCountMap = new HashMap(); + HashMap operatorCount = new HashMap(); Iterator constraintIterator = Constraints.iterator(); while (constraintIterator.hasNext()) { - HashMap currentOperatorCountMap = Trees.traverse( + HashMap currentOperatorCount = Trees.traverse( constraintIterator.next().getFormula(), new OperatorDistribution()) .orElse(new HashMap()); - currentOperatorCountMap.forEach((key, value) -> { - if (operatorCountMap.containsKey(key)) { - operatorCountMap.replace(key, operatorCountMap.get(key) + value); + currentOperatorCount.forEach((key, value) -> { + if (operatorCount.containsKey(key)) { + operatorCount.replace(key, operatorCount.get(key) + value); } else { - operatorCountMap.put(key, value); + operatorCount.put(key, value); } }); } - return Result.of(operatorCountMap); + return Result.of(operatorCount); } } diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index 08f3dd55..aee6b722 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -43,25 +43,25 @@ public class ConstraintPropertiesTest { @Test public void atomsTest() { FeatureModel featureModel = createFeatureModel(); - IComputation compuational = Computations.of(featureModel).map(ComputeConstraintProperties::new); + IComputation compuational = Computations.of(featureModel).map(ComputeAtomsCount::new); assertEquals(21, compuational.compute()); assertEquals( 3, compuational - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.FALSE) .compute()); assertEquals( 18, compuational - .set(ComputeConstraintProperties.COUNTCONSTANTS, Boolean.FALSE) - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.TRUE) + .set(ComputeAtomsCount.COUNTCONSTANTS, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.TRUE) .compute()); assertEquals( 0, compuational - .set(ComputeConstraintProperties.COUNTCONSTANTS, Boolean.FALSE) - .set(ComputeConstraintProperties.COUNTVARIABLES, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTCONSTANTS, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.FALSE) .compute()); } From 2e0ed15b1e8728123971cf584b97091c7805e1a3 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Sun, 5 Oct 2025 11:42:23 +0200 Subject: [PATCH 043/257] chores: resolved star imports to specific imports. removed console prints and temporary comments. --- .../model/computation/ConstraintPropertiesTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index aee6b722..eddd61b3 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -25,10 +25,14 @@ import static de.featjar.formula.structure.Expressions.variable; import static org.junit.jupiter.api.Assertions.assertEquals; -import de.featjar.base.computation.*; +import de.featjar.base.computation.Computations; +import de.featjar.base.computation.IComputation; import de.featjar.feature.model.FeatureModel; import de.featjar.formula.structure.IFormula; -import de.featjar.formula.structure.connective.*; +import de.featjar.formula.structure.connective.And; +import de.featjar.formula.structure.connective.Implies; +import de.featjar.formula.structure.connective.Not; +import de.featjar.formula.structure.connective.Or; import de.featjar.formula.structure.predicate.Equals; import de.featjar.formula.structure.predicate.Literal; import de.featjar.formula.structure.term.ITerm; @@ -37,8 +41,6 @@ import org.junit.jupiter.api.Test; public class ConstraintPropertiesTest { - // FeatJAR.(); - // FeatureJAR.log(); @Test public void atomsTest() { @@ -79,7 +81,6 @@ public void operatorDensityTest() { HashMap computational = Computations.of(featureModel) .map(ComputeOperatorDistribution::new) .compute(); - System.out.println(computational); assertEquals(4, computational.get("And")); assertEquals(3, computational.get("Or")); assertEquals(2, computational.get("Not")); @@ -91,7 +92,6 @@ public void AverageConstraint() { FeatureModel featureModel = createFeatureModel(); float computational = Computations.of(featureModel).map(ComputeAverageConstraint::new).compute(); - System.out.println(computational); assertEquals(21.0 / 3.0, computational); } From 3b92cd50c2430ca5109d7ea98dabb23dd2c9780f Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Mon, 6 Oct 2025 10:03:47 +0200 Subject: [PATCH 044/257] docs: added documentation to the classes Visitors and Computational for the formula analysis part. feat: Added counting for True and False instances in the class AtomsCount. test: modified the tests slightly to account for the changes in the class AtomsCount. --- .../feature/model/analysis/AtomsCount.java | 9 +++++++- .../model/analysis/FeatureDensity.java | 1 + .../model/analysis/OperatorDistribution.java | 1 + .../model/computation/ComputeAtomsCount.java | 20 ++++++++++++++++-- .../computation/ComputeAverageConstraint.java | 21 +++++++++++++++++-- .../computation/ComputeFeatureDensity.java | 7 +++++++ .../ComputeOperatorDistribution.java | 7 +++++++ .../computation/ConstraintPropertiesTest.java | 13 ++++++++---- 8 files changed, 70 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java index 31824ee1..f2e8f168 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java +++ b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java @@ -23,6 +23,8 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.formula.structure.predicate.False; +import de.featjar.formula.structure.predicate.True; import de.featjar.formula.structure.term.value.Constant; import de.featjar.formula.structure.term.value.Variable; import java.util.List; @@ -30,6 +32,7 @@ /** * Counts the number of used variables and constants in a tree. * By default, both are counted, but it can be set to count only one of the two. + * For further information on its methods see {@link ITreeVisitor} * * @author Mohammad Khair Almekkawi * @author Florian Beese @@ -38,10 +41,12 @@ public class AtomsCount implements ITreeVisitor, Integer> { private int atomsCount = 0; private boolean countVariables = true; private boolean countConstants = true; + private boolean countBoolean = true; - public AtomsCount(boolean countVariables, boolean countConstants) { + public AtomsCount(boolean countVariables, boolean countConstants, boolean countBoolean) { this.countConstants = countConstants; this.countVariables = countVariables; + this.countBoolean = countBoolean; } @Override @@ -51,6 +56,8 @@ public TraversalAction firstVisit(List> path) { atomsCount++; } else if (countVariables && node instanceof Variable) { atomsCount++; + } else if (countBoolean && (node instanceof True || node instanceof False)) { + atomsCount++; } return TraversalAction.CONTINUE; } diff --git a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java index fb7aeb40..b9de92cc 100644 --- a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java @@ -30,6 +30,7 @@ /** * Enumerates the names of all distinct variables occurring in a tree. + * For further information on its methods see {@link ITreeVisitor} * * @author Mohammad Khair Almekkawi * @author Florian Beese diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java index 6cd061f3..50973081 100644 --- a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -29,6 +29,7 @@ /** * Counts the the absolute occurrence of different operators in a tree. + * For further information on its methods see {@link ITreeVisitor} * * @author Mohammad Khair Almekkawi * @author Florian Beese diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index 35c6916f..587ac43f 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -34,13 +34,27 @@ import java.util.Iterator; import java.util.List; +/** + * Call the visitor AtomsCount on all constraints of a feature model to count the terminal expressions. + * It is possible to set the three variables COUNTCONSTANTS, COUNTVARIABLES and COUNTBOOLEAN in order + * to set what should be counted + * For further information on its methods see {@link IComputation} + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + * */ public class ComputeAtomsCount extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); public ComputeAtomsCount(IComputation featureModel) { - super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); + super( + featureModel, + Computations.of(Boolean.TRUE), + Computations.of(Boolean.TRUE), + Computations.of(Boolean.TRUE)); } @Override @@ -54,7 +68,9 @@ public Result compute(List dependencyList, Progress progress) { + Trees.traverse( constraintIterator.next().getFormula(), new AtomsCount( - COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) + COUNTVARIABLES.get(dependencyList), + COUNTCONSTANTS.get(dependencyList), + COUNTBOOLEAN.get(dependencyList))) .orElse(0); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 4e9e4d82..34a5ff9b 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -34,13 +34,28 @@ import java.util.Iterator; import java.util.List; +/** + * Call the visitor AtomsCount on all constraints of a feature model to count the terminal expressions and then + * compute the average in relation to the count of constraints of a feature model. + * It is possible to set the three variables COUNTCONSTANTS, COUNTVARIABLES and COUNTBOOLEAN in order + * to set what should be counted. + * For further information on its methods see {@link IComputation} + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + * */ public class ComputeAverageConstraint extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); public ComputeAverageConstraint(IComputation featureModel) { - super(featureModel, Computations.of(Boolean.TRUE), Computations.of(Boolean.TRUE)); + super( + featureModel, + Computations.of(Boolean.TRUE), + Computations.of(Boolean.TRUE), + Computations.of(Boolean.TRUE)); } @Override @@ -55,7 +70,9 @@ public Result compute(List dependencyList, Progress progress) { + Trees.traverse( constraintIterator.next().getFormula(), new AtomsCount( - COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList))) + COUNTVARIABLES.get(dependencyList), + COUNTCONSTANTS.get(dependencyList), + COUNTBOOLEAN.get(dependencyList))) .orElse(0); } return Result.of( diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java index ede39092..95c71388 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -36,6 +36,13 @@ import java.util.List; import java.util.Set; +/** + * Call the visitor FeatureDensity on all constraints of a feature model to get the density of the used features in the constraints. + * For further information on its methods see {@link IComputation} + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + * */ public class ComputeFeatureDensity extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java index 499031c6..8832cb7c 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -34,6 +34,13 @@ import java.util.Iterator; import java.util.List; +/** + * Call the visitor OperatorDistribution on all constraints of a feature model to get the count of each nonterminal operation(and, or,...). + * For further information on its methods see {@link IComputation} + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + * */ public class ComputeOperatorDistribution extends AComputation> { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index eddd61b3..854a7aed 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -35,6 +35,7 @@ import de.featjar.formula.structure.connective.Or; import de.featjar.formula.structure.predicate.Equals; import de.featjar.formula.structure.predicate.Literal; +import de.featjar.formula.structure.predicate.True; import de.featjar.formula.structure.term.ITerm; import de.featjar.formula.structure.term.value.Constant; import java.util.HashMap; @@ -47,23 +48,26 @@ public void atomsTest() { FeatureModel featureModel = createFeatureModel(); IComputation compuational = Computations.of(featureModel).map(ComputeAtomsCount::new); - assertEquals(21, compuational.compute()); + assertEquals(23, compuational.compute()); assertEquals( 3, compuational .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTBOOLEAN, Boolean.FALSE) .compute()); assertEquals( 18, compuational .set(ComputeAtomsCount.COUNTCONSTANTS, Boolean.FALSE) .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.TRUE) + .set(ComputeAtomsCount.COUNTBOOLEAN, Boolean.FALSE) .compute()); assertEquals( 0, compuational .set(ComputeAtomsCount.COUNTCONSTANTS, Boolean.FALSE) .set(ComputeAtomsCount.COUNTVARIABLES, Boolean.FALSE) + .set(ComputeAtomsCount.COUNTBOOLEAN, Boolean.FALSE) .compute()); } @@ -72,7 +76,7 @@ public void featureDensityTest() { FeatureModel featureModel = createFeatureModel(); float computational = Computations.of(featureModel).map(ComputeFeatureDensity::new).compute(); - assertEquals((float) 6.0 / (float) 7.0, computational); + assertEquals((float) 6 / (float) 7, computational); } // operator((and,4), (or, 3), (not, 2), (implies, 3)) @Test @@ -92,7 +96,7 @@ public void AverageConstraint() { FeatureModel featureModel = createFeatureModel(); float computational = Computations.of(featureModel).map(ComputeAverageConstraint::new).compute(); - assertEquals(21.0 / 3.0, computational); + assertEquals((float) 23 / (float) 3, computational); } public FeatureModel createFeatureModel() { @@ -133,7 +137,8 @@ public FeatureModel createFeatureModel() { new Or(literalA, literalB, literalI), new Not(literalB), new Implies(literalK, literalO), - new And(literalB)); + new And(literalB), + True.INSTANCE); // 9 literal, 7 operator((and,2), (or, 2), (not, 1), (implies, 2)), Features(a,b,i,k,o) IFormula formula2 = new Or(new Implies(formula1, literalO)); // 1 literal, 0 operator, 3 constants From f883610d822fb364c367737d5be6fcf935f1a1e4 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Mon, 6 Oct 2025 13:24:48 +0200 Subject: [PATCH 045/257] docs: added some extra docs. fix: corrected the OperatorDistribution, so that it does not count Reference class --- .../java/de/featjar/feature/model/analysis/AtomsCount.java | 6 ++++++ .../feature/model/analysis/OperatorDistribution.java | 5 ++++- .../feature/model/computation/ComputeAtomsCount.java | 4 ++++ 3 files changed, 14 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java index f2e8f168..cf37c9b0 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java +++ b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java @@ -43,6 +43,12 @@ public class AtomsCount implements ITreeVisitor, Integer> { private boolean countConstants = true; private boolean countBoolean = true; + /** + * + * @param countVariables decide if Atoms of type variable should be counted + * @param countConstants decide if Atoms of type constants should be counted + * @param countBoolean decide if Atoms of type True or False should be counted + */ public AtomsCount(boolean countVariables, boolean countConstants, boolean countBoolean) { this.countConstants = countConstants; this.countVariables = countVariables; diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java index 50973081..8f97caeb 100644 --- a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -24,6 +24,8 @@ import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.formula.structure.connective.IConnective; +import de.featjar.formula.structure.connective.Reference; + import java.util.HashMap; import java.util.List; @@ -35,12 +37,13 @@ * @author Florian Beese */ public class OperatorDistribution implements ITreeVisitor, HashMap> { + // Saves the count of each operator, where each key is the name of the class of the operator HashMap operatorCount = new HashMap(); @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); - if (node instanceof IConnective) { + if (node instanceof IConnective && ! (node instanceof Reference)) { String nodeKey = node.getClass().getSimpleName(); if (!operatorCount.containsKey(nodeKey)) { operatorCount.put(nodeKey, 1); diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index 587ac43f..44bf884f 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -45,10 +45,14 @@ * */ public class ComputeAtomsCount extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + //COUNTCONSTANTS decide if Atoms of type constants should be counted protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + //COUNTVARIABLES decide if Atoms of type variable should be counted protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + //COUNTBOOLEAN decide if Atoms of type True or False should be counted protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); + public ComputeAtomsCount(IComputation featureModel) { super( featureModel, From 4ebb6d52f7be4cc7c69c4a955634aa719e7febc4 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 1 Oct 2025 15:22:24 +0200 Subject: [PATCH 046/257] feat basic start for Simple Tree Properties --- .../model/analysis/SimpleTreeProperties.java | 57 +++++++++++++++++++ 1 file changed, 57 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java new file mode 100644 index 00000000..5026ac0f --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -0,0 +1,57 @@ +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.base.tree.Trees; +import de.featjar.base.tree.visitor.TreeDepthCounter; +import de.featjar.feature.model.*; + +public class SimpleTreeProperties { + + // todo + public int topFeatures(IFeatureTree tree) { + + return tree.getRoot().getChildrenCount(); // do groups count as features? + } + + public int leafFeatures(IFeatureTree tree) { + return 0; + } + + public int treeDepth(IFeatureTree tree) { + TreeDepthCounter visitor = new TreeDepthCounter(); + Result result = Trees.traverse(tree.getRoot(),visitor); + return result.get(); + } + + public void testMethode() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + //rootTree.mutate().toAndGroup(); + IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); + IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); + + IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); + IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); + + /* + TreePrinter visitor = new TreePrinter(); + Result traverseResult = Trees.traverse(rootTree, visitor); + System.out.println(traverseResult.get()); + */ + + int depth = treeDepth(rootTree); + System.out.println(depth); + + + } + + public static void main(String[] args){ + + SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); + simpleTreeProperties.testMethode(); + + } + +} From cf49e343965a7b6ff0bf12c9e2c9c2a94f449271 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 1 Oct 2025 15:23:55 +0200 Subject: [PATCH 047/257] feat basic start for Simple Tree Properties --- .../de/featjar/feature/model/analysis/SimpleTreeProperties.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index 5026ac0f..022edc72 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -8,7 +8,6 @@ public class SimpleTreeProperties { - // todo public int topFeatures(IFeatureTree tree) { return tree.getRoot().getChildrenCount(); // do groups count as features? From 7788b6d8ee95de557e87c1d3b2a0dc3918bccf19 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 1 Oct 2025 16:39:42 +0200 Subject: [PATCH 048/257] feat visitor implementation of leaf counter --- .../model/analysis/SimpleTreeProperties.java | 35 ++++++++-- .../model/analysis/TreeLeafCounter.java | 67 +++++++++++++++++++ 2 files changed, 98 insertions(+), 4 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index 022edc72..6fe4ade2 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -6,6 +6,8 @@ import de.featjar.base.tree.visitor.TreeDepthCounter; import de.featjar.feature.model.*; +import java.util.List; + public class SimpleTreeProperties { public int topFeatures(IFeatureTree tree) { @@ -13,10 +15,34 @@ public int topFeatures(IFeatureTree tree) { return tree.getRoot().getChildrenCount(); // do groups count as features? } - public int leafFeatures(IFeatureTree tree) { - return 0; + /* + + public int leafFeaturesRecursive(IFeatureTree currentNode) { + + List children = currentNode.getChildren(); + if (children.isEmpty()) { + return 1; + } + + int result = 0; + for (IFeatureTree child : children) { + result += leafFeaturesRecursive(child); + } + return result; + } + + public int leafFeaturesStarter(IFeatureTree tree) { + return this.leafFeaturesRecursive(tree.getRoot()); } + */ + + public int leafFeaturesCounter(IFeatureTree tree) { + Result traverseResult = Trees.traverse(tree, new TreeLeafCounter()); + return traverseResult.get(); + } + + public int treeDepth(IFeatureTree tree) { TreeDepthCounter visitor = new TreeDepthCounter(); Result result = Trees.traverse(tree.getRoot(),visitor); @@ -40,8 +66,9 @@ public void testMethode() { System.out.println(traverseResult.get()); */ - int depth = treeDepth(rootTree); - System.out.println(depth); + // int depth = treeDepth(rootTree); + int leafCount = leafFeaturesCounter(rootTree); + System.out.println(leafCount); } diff --git a/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java new file mode 100644 index 00000000..3ba95539 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ + +package de.featjar.feature.model.analysis; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; + +import java.util.List; + +/** + * Counts the number of nodes that have no child nodes + * Can be passed a class up to which should be counted (e.g., to exclude details in a tree). + * + * @author Sebastian Krieter + */ +public class TreeLeafCounter implements ITreeVisitor, Integer> { + private Class> terminalClass = null; + private int leafCount = 0; + + public Class> getTerminalClass() { + return terminalClass; + } + + public void setTerminalClass(Class> terminalClass) { + this.terminalClass = terminalClass; + } + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + if (!node.hasChildren()) { + leafCount++; + } + return TraversalAction.CONTINUE; + + } + + @Override + public void reset() { + leafCount = 0; + } + + @Override + public Result getResult() { + return Result.of(leafCount); + } +} From 044ce33a70d0c7547820c19c9806b067390cff11 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 11:21:31 +0200 Subject: [PATCH 049/257] feat: "average number of children" implemented + docs and tests for "top features", "leaf features", "tree depth" --- .../model/analysis/SimpleTreeProperties.java | 93 +++++++++---------- .../visitor/FeatureTreeGroupCounter.java | 69 ++++++++++++++ .../visitor/TreeAvgChildrenCounter.java | 82 ++++++++++++++++ .../{ => visitor}/TreeLeafCounter.java | 2 +- .../analysis/SimpleTreePropertiesTest.java | 65 +++++++++++++ 5 files changed, 263 insertions(+), 48 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java rename src/main/java/de/featjar/feature/model/analysis/{ => visitor}/TreeLeafCounter.java (97%) create mode 100644 src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index 6fe4ade2..b01e4e33 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -5,79 +5,78 @@ import de.featjar.base.tree.Trees; import de.featjar.base.tree.visitor.TreeDepthCounter; import de.featjar.feature.model.*; +import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; +import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; import java.util.List; public class SimpleTreeProperties { - public int topFeatures(IFeatureTree tree) { - - return tree.getRoot().getChildrenCount(); // do groups count as features? - } - - /* - - public int leafFeaturesRecursive(IFeatureTree currentNode) { - - List children = currentNode.getChildren(); - if (children.isEmpty()) { - return 1; - } - - int result = 0; - for (IFeatureTree child : children) { - result += leafFeaturesRecursive(child); - } - return result; + /** + * Automatically finds the root of the given subtree. + * @param tree: feature tree (whose root will be found automatically) + * @return number of features directly below the root of this tree. + */ + public Result topFeatures(IFeatureTree tree) { + int childrenCount = tree.getRoot().getChildrenCount(); + return Result.of(childrenCount); } - public int leafFeaturesStarter(IFeatureTree tree) { - return this.leafFeaturesRecursive(tree.getRoot()); + /** + * @param tree: feature tree + * @return the number of features that have no child features + */ + public Result leafFeaturesCounter(IFeatureTree tree) { + return Trees.traverse(tree, new TreeLeafCounter()); } + /** + * @param tree: feature tree + * @return tree depth, meaning the longest path from this subtree's root to its most distant leaf node */ - - public int leafFeaturesCounter(IFeatureTree tree) { - Result traverseResult = Trees.traverse(tree, new TreeLeafCounter()); - return traverseResult.get(); + public Result treeDepth(IFeatureTree tree) { + TreeDepthCounter visitor = new TreeDepthCounter(); + return Trees.traverse(tree,visitor); } - - public int treeDepth(IFeatureTree tree) { - TreeDepthCounter visitor = new TreeDepthCounter(); - Result result = Trees.traverse(tree.getRoot(),visitor); - return result.get(); + /** + * @param tree: feature tree + * @return average number of children that each node in the tree has, rounded to integer. + */ + public Result avgNumberOfChildren(IFeatureTree tree) { + TreeAvgChildrenCounter visitor = new TreeAvgChildrenCounter(); + return Trees.traverse(tree,visitor); } - public void testMethode() { + // work in progress + public void groupDistribution() { + // build a sample tree FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); IFeatureTree rootTree = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - //rootTree.mutate().toAndGroup(); + rootTree.mutate().toAlternativeGroup(); + IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); - - /* - TreePrinter visitor = new TreePrinter(); - Result traverseResult = Trees.traverse(rootTree, visitor); - System.out.println(traverseResult.get()); - */ - - // int depth = treeDepth(rootTree); - int leafCount = leafFeaturesCounter(rootTree); - System.out.println(leafCount); - + IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); + IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); + + // check subtree for groups + List children = rootTree.getChildrenGroups(); + for (FeatureTree.Group child : children) { + boolean isAnd = child.isAlternative(); + System.out.println(isAnd); + } } public static void main(String[] args){ - + // still have to make all the functions return Results, not ints + // still have to write docs SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); - simpleTreeProperties.testMethode(); + simpleTreeProperties.groupDistribution(); } - } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java new file mode 100644 index 00000000..b11ae520 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ + +package de.featjar.feature.model.analysis.visitor; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; + +import java.util.List; + +/** + * Counts the share of groups found in the given feature tree, in order: Or, And, Alternate. + * + * @author Sebastian Krieter + */ +public class FeatureTreeGroupCounter implements ITreeVisitor, Integer> { + private Class> terminalClass = null; + private int andGroupCount = 0; + private int orGroupCount = 0; + private int altGroupCount = 0; + private int leafCount = 0; + + public Class> getTerminalClass() { + return terminalClass; + } + + public void setTerminalClass(Class> terminalClass) { + this.terminalClass = terminalClass; + } + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + + + + return TraversalAction.CONTINUE; + + } + + @Override + public void reset() { + leafCount = 0; + } + + @Override + public Result getResult() { + return Result.of(leafCount); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java new file mode 100644 index 00000000..8a499379 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-base. + * + * base is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * base is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with base. If not, see . + * + * See for further information. + */ + +package de.featjar.feature.model.analysis.visitor; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.structure.ITree; +import de.featjar.base.tree.visitor.ITreeVisitor; + +import java.util.List; + +/** + * Calculates the average amount of children per node in the tree. + * Returns 0 if tree has no nodes. + * + * @author Sebastian Krieter + */ +public class TreeAvgChildrenCounter implements ITreeVisitor, Integer> { + private Class> terminalClass = null; + private int nodeCount = 0; + private int childCount = 0; + + public Class> getTerminalClass() { + return terminalClass; + } + + public void setTerminalClass(Class> terminalClass) { + this.terminalClass = terminalClass; + } + + @Override + public TraversalAction firstVisit(List> path) { + final ITree node = ITreeVisitor.getCurrentNode(path); + + nodeCount++; + childCount += node.getChildrenCount(); + + // if only nodes with children should be counted + /* + if (node.hasChildren()) { + nodeCount++; + childCount += node.getChildrenCount(); + } + */ + + return TraversalAction.CONTINUE; + } + + @Override + public void reset() { + nodeCount = 0; + childCount = 0; + } + + @Override + public Result getResult() { + // currently uses int because float and double are illegal inputs for Result.of() + int result = 0; + if (nodeCount > 0) { + result = childCount /nodeCount; + } + return Result.of(result); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java similarity index 97% rename from src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java rename to src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java index 3ba95539..0e89d3c7 100644 --- a/src/main/java/de/featjar/feature/model/analysis/TreeLeafCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java @@ -19,7 +19,7 @@ * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java new file mode 100644 index 00000000..499a8a1d --- /dev/null +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -0,0 +1,65 @@ +package de.featjar.feature.model.analysis; +import static org.junit.jupiter.api.Assertions.*; + +import de.featjar.Common; +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.FeatureTree; +import de.featjar.feature.model.IFeature; +import de.featjar.feature.model.IFeatureTree; + +import org.junit.jupiter.api.Test; + +import java.util.List; + +public class SimpleTreePropertiesTest extends Common { + SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); + IFeatureTree smallTree = generateSmallTree(); + + /** + * Creates a tree with a root node that has 1 child, and this child has 2 more children + * @return a small feature tree for testing purposes + */ + public IFeatureTree generateSmallTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + rootTree.mutate().toAlternativeGroup(); + + IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); + IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); + IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); + IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); + IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); + IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); + + + return rootTree; + } + + @Test + void testTopFeatures() { + int rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); + assertEquals(1, rootChildren); + } + + @Test + void testLeafFeaturesCounter() { + int leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); + assertEquals(2, leaves); + } + + @Test + void testTreeDepth() { + int depth = simpleTreeProperties.treeDepth(smallTree).get(); + assertEquals(3, depth); + } + + @Test + void testAvgNumberOfChildren() { + int average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); + assertEquals(0, average); + } + + +} From 49535b6ecce551eedef211b8ab7e6bc1578bbf82 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 14:12:06 +0200 Subject: [PATCH 050/257] feat: "group distribution" implemented. --- .../model/analysis/SimpleTreeProperties.java | 42 +++++------------- .../visitor/FeatureTreeGroupCounter.java | 43 ++++++++++++------- .../visitor/TreeAvgChildrenCounter.java | 16 +++---- .../analysis/SimpleTreePropertiesTest.java | 17 +++++--- 4 files changed, 54 insertions(+), 64 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index b01e4e33..2da439f2 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -1,14 +1,14 @@ package de.featjar.feature.model.analysis; import de.featjar.base.data.Result; -import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.tree.Trees; import de.featjar.base.tree.visitor.TreeDepthCounter; import de.featjar.feature.model.*; +import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; -import java.util.List; +import java.util.HashMap; public class SimpleTreeProperties { @@ -43,40 +43,18 @@ public Result treeDepth(IFeatureTree tree) { * @param tree: feature tree * @return average number of children that each node in the tree has, rounded to integer. */ - public Result avgNumberOfChildren(IFeatureTree tree) { + public Result avgNumberOfChildren(IFeatureTree tree) { TreeAvgChildrenCounter visitor = new TreeAvgChildrenCounter(); return Trees.traverse(tree,visitor); } - // work in progress - public void groupDistribution() { - // build a sample tree - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree rootTree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - rootTree.mutate().toAlternativeGroup(); - - IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); - IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); - IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); - IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); - IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); - - // check subtree for groups - List children = rootTree.getChildrenGroups(); - for (FeatureTree.Group child : children) { - boolean isAnd = child.isAlternative(); - System.out.println(isAnd); - } - + /** Counts the number of different groups in this tree. + * @param tree: feature tree + * @return hashmap with the String keys "AlternativeGroup", "OrGroup" and "AndGroup" to get the respective counts + */ + public Result> groupDistribution(IFeatureTree tree) { + FeatureTreeGroupCounter visitor = new FeatureTreeGroupCounter(); + return Trees.traverse(tree,visitor); } - public static void main(String[] args){ - // still have to make all the functions return Results, not ints - // still have to write docs - SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); - simpleTreeProperties.groupDistribution(); - - } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index b11ae520..e134062f 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -24,7 +24,10 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.feature.model.FeatureTree; +import de.featjar.feature.model.IFeatureTree; +import java.util.HashMap; import java.util.List; /** @@ -32,38 +35,48 @@ * * @author Sebastian Krieter */ -public class FeatureTreeGroupCounter implements ITreeVisitor, Integer> { - private Class> terminalClass = null; - private int andGroupCount = 0; - private int orGroupCount = 0; - private int altGroupCount = 0; - private int leafCount = 0; +public class FeatureTreeGroupCounter implements ITreeVisitor, HashMap> { + private Class> terminalClass = null; + int altCounter = 0, orCounter = 0, andCounter = 0; - public Class> getTerminalClass() { + public Class> getTerminalClass() { return terminalClass; } - public void setTerminalClass(Class> terminalClass) { + public void setTerminalClass(Class> terminalClass) { this.terminalClass = terminalClass; } @Override - public TraversalAction firstVisit(List> path) { - final ITree node = ITreeVisitor.getCurrentNode(path); - + public TraversalAction firstVisit(List> path) { + final IFeatureTree tree = (IFeatureTree) ITreeVisitor.getCurrentNode(path); + for (FeatureTree.Group group : tree.getChildrenGroups()) { + if (group.isAlternative()) { + altCounter++; + } else if (group.isOr()) { + orCounter++; + } else if (group.isAnd()) { + andCounter++; + } + } return TraversalAction.CONTINUE; - } @Override public void reset() { - leafCount = 0; + altCounter = 0; + orCounter = 0; + andCounter = 0; } @Override - public Result getResult() { - return Result.of(leafCount); + public Result> getResult() { + HashMap countedGroups = new HashMap<>(); + countedGroups.put("AlternativeGroup", altCounter); + countedGroups.put("OrGroup", orCounter); + countedGroups.put("AndGroup", andCounter); + return Result.of(countedGroups); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index 8a499379..ceb3eb7c 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -33,7 +33,7 @@ * * @author Sebastian Krieter */ -public class TreeAvgChildrenCounter implements ITreeVisitor, Integer> { +public class TreeAvgChildrenCounter implements ITreeVisitor, Float> { private Class> terminalClass = null; private int nodeCount = 0; private int childCount = 0; @@ -50,16 +50,10 @@ public void setTerminalClass(Class> terminalClass) { public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); + nodeCount++; childCount += node.getChildrenCount(); - // if only nodes with children should be counted - /* - if (node.hasChildren()) { - nodeCount++; - childCount += node.getChildrenCount(); - } - */ return TraversalAction.CONTINUE; } @@ -71,11 +65,11 @@ public void reset() { } @Override - public Result getResult() { + public Result getResult() { // currently uses int because float and double are illegal inputs for Result.of() - int result = 0; + float result = 0; if (nodeCount > 0) { - result = childCount /nodeCount; + result = (float) childCount /nodeCount; } return Result.of(result); } diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 499a8a1d..f7cd86b6 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -4,13 +4,12 @@ import de.featjar.Common; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; import org.junit.jupiter.api.Test; -import java.util.List; +import java.util.HashMap; public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); @@ -20,7 +19,7 @@ public class SimpleTreePropertiesTest extends Common { * Creates a tree with a root node that has 1 child, and this child has 2 more children * @return a small feature tree for testing purposes */ - public IFeatureTree generateSmallTree() { + private IFeatureTree generateSmallTree() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); IFeatureTree rootTree = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); @@ -33,7 +32,6 @@ public IFeatureTree generateSmallTree() { IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); - return rootTree; } @@ -57,9 +55,16 @@ void testTreeDepth() { @Test void testAvgNumberOfChildren() { - int average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); - assertEquals(0, average); + float average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); + assertEquals(0.75, average); } + @Test + void testGroupDistribution() { + HashMap groupCounts = simpleTreeProperties.groupDistribution(smallTree).get(); + assertEquals(1, groupCounts.get("AlternativeGroup")); + assertEquals(3, groupCounts.get("AndGroup")); + assertEquals(0, groupCounts.get("OrGroup")); + } } From 725497158500e4809e48de71a3ce78efcef8b90d Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 14:57:05 +0200 Subject: [PATCH 051/257] refactor: deleted unnecessary code copied from templates --- .../analysis/visitor/FeatureTreeGroupCounter.java | 9 --------- .../analysis/visitor/TreeAvgChildrenCounter.java | 14 -------------- .../model/analysis/visitor/TreeLeafCounter.java | 9 --------- 3 files changed, 32 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index e134062f..a9e4c633 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -36,17 +36,8 @@ * @author Sebastian Krieter */ public class FeatureTreeGroupCounter implements ITreeVisitor, HashMap> { - private Class> terminalClass = null; int altCounter = 0, orCounter = 0, andCounter = 0; - public Class> getTerminalClass() { - return terminalClass; - } - - public void setTerminalClass(Class> terminalClass) { - this.terminalClass = terminalClass; - } - @Override public TraversalAction firstVisit(List> path) { final IFeatureTree tree = (IFeatureTree) ITreeVisitor.getCurrentNode(path); diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index ceb3eb7c..9b632406 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -34,27 +34,14 @@ * @author Sebastian Krieter */ public class TreeAvgChildrenCounter implements ITreeVisitor, Float> { - private Class> terminalClass = null; private int nodeCount = 0; private int childCount = 0; - public Class> getTerminalClass() { - return terminalClass; - } - - public void setTerminalClass(Class> terminalClass) { - this.terminalClass = terminalClass; - } - @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); - - nodeCount++; childCount += node.getChildrenCount(); - - return TraversalAction.CONTINUE; } @@ -66,7 +53,6 @@ public void reset() { @Override public Result getResult() { - // currently uses int because float and double are illegal inputs for Result.of() float result = 0; if (nodeCount > 0) { result = (float) childCount /nodeCount; diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java index 0e89d3c7..a6d37e80 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java @@ -34,17 +34,8 @@ * @author Sebastian Krieter */ public class TreeLeafCounter implements ITreeVisitor, Integer> { - private Class> terminalClass = null; private int leafCount = 0; - public Class> getTerminalClass() { - return terminalClass; - } - - public void setTerminalClass(Class> terminalClass) { - this.terminalClass = terminalClass; - } - @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); From 491f451239b7cddff288849844d73073f84c7b35 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 14:57:40 +0200 Subject: [PATCH 052/257] test: work towards a tree generator that maps onto the example one from the software product line test course --- .../analysis/SimpleTreePropertiesTest.java | 75 ++++++++++++++++++- 1 file changed, 73 insertions(+), 2 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index f7cd86b6..c426aeea 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -4,6 +4,7 @@ import de.featjar.Common; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; @@ -28,13 +29,59 @@ private IFeatureTree generateSmallTree() { IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); - IFeatureTree childTree2 = childTree1.mutate().addFeatureBelow(childFeature2); + childTree1.mutate().addFeatureBelow(childFeature2); IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); - IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); + childTree1.mutate().addFeatureBelow(childFeature3); return rootTree; } + private IFeatureTree generateMediumTree() { + // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree treeRoot = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); + + IFeature featureAPI = featureModel.mutate().addFeature("API"); + IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); + treeAPI.isMandatory(); + // treeRoot.addChild(treeAPI); + + IFeature featureGet = featureModel.mutate().addFeature("Get"); + IFeatureTree treeGet = treeAPI.mutate().addFeatureBelow(featureGet); + //treeAPI.addChild(treeGet); + + IFeature featurePut = featureModel.mutate().addFeature("Put"); + IFeatureTree treePut = treeAPI.mutate().addFeatureBelow(featurePut); + //treeAPI.addChild(treePut); + + IFeature featureDelete = featureModel.mutate().addFeature("Delete"); + IFeatureTree treeDelete = treeAPI.mutate().addFeatureBelow(featureDelete); + //treeAPI.addChild(treeDelete); + treeAPI.mutate().toOrGroup(); + + IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); + IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); + treeTransactions.isOptional(); + + IFeature featureOS = featureModel.mutate().addFeature("OS"); + IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); + treeOS.isMandatory(); + //treeRoot.addChild(treeOS); + + IFeature featureWindows = featureModel.mutate().addFeature("Windows"); + IFeatureTree treeWindows = treeOS.mutate().addFeatureBelow(featureWindows); + //treeOS.addChild(treeWindows); + + IFeature featureLinux = featureModel.mutate().addFeature("Linux"); + IFeatureTree treeLinux = treeOS.mutate().addFeatureBelow(featureLinux); + treeOS.mutate().toAlternativeGroup(); + //treeOS.addChild(treeLinux); + + return treeRoot; + + } + @Test void testTopFeatures() { int rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); @@ -67,4 +114,28 @@ void testGroupDistribution() { assertEquals(0, groupCounts.get("OrGroup")); } + @Test + // to be deleted. This is to find out why everything in our tests has an and group + void smallTest() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + + HashMap groupCounts = simpleTreeProperties.groupDistribution(rootTree).get(); + System.out.println(groupCounts); + + for (FeatureTree.Group group : rootTree.getChildrenGroups()) { + System.out.println(group.isAnd()); + } + System.out.println(); + + } + + @Test + void mediumTest() { + IFeatureTree tree = generateMediumTree(); + HashMap groupCounts = simpleTreeProperties.groupDistribution(tree).get(); + System.out.println(groupCounts); + } + } From 2e4cec9ed8e8cf295f513521fc54fd55d56c523d Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 15:11:38 +0200 Subject: [PATCH 053/257] style: used gradle formatter --- .../model/analysis/SimpleTreeProperties.java | 39 +++++--- .../visitor/FeatureTreeGroupCounter.java | 12 +-- .../visitor/TreeAvgChildrenCounter.java | 14 ++- .../analysis/visitor/TreeLeafCounter.java | 13 +-- .../analysis/SimpleTreePropertiesTest.java | 95 +++++++++++++++---- 5 files changed, 122 insertions(+), 51 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index 2da439f2..c108a66d 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.analysis; import de.featjar.base.data.Result; @@ -7,18 +27,17 @@ import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; - import java.util.HashMap; public class SimpleTreeProperties { /** - * Automatically finds the root of the given subtree. - * @param tree: feature tree (whose root will be found automatically) - * @return number of features directly below the root of this tree. + * @param tree: feature tree + * @return number of features directly below the root of this subtree. */ public Result topFeatures(IFeatureTree tree) { - int childrenCount = tree.getRoot().getChildrenCount(); + // int childrenCount = tree.getRoot().getChildrenCount(); // if we should find a subtree's root automatically + int childrenCount = tree.getChildrenCount(); return Result.of(childrenCount); } @@ -35,8 +54,7 @@ public Result leafFeaturesCounter(IFeatureTree tree) { * @return tree depth, meaning the longest path from this subtree's root to its most distant leaf node */ public Result treeDepth(IFeatureTree tree) { - TreeDepthCounter visitor = new TreeDepthCounter(); - return Trees.traverse(tree,visitor); + return Trees.traverse(tree, new TreeDepthCounter()); } /** @@ -44,8 +62,7 @@ public Result treeDepth(IFeatureTree tree) { * @return average number of children that each node in the tree has, rounded to integer. */ public Result avgNumberOfChildren(IFeatureTree tree) { - TreeAvgChildrenCounter visitor = new TreeAvgChildrenCounter(); - return Trees.traverse(tree,visitor); + return Trees.traverse(tree, new TreeAvgChildrenCounter()); } /** Counts the number of different groups in this tree. @@ -53,8 +70,6 @@ public Result avgNumberOfChildren(IFeatureTree tree) { * @return hashmap with the String keys "AlternativeGroup", "OrGroup" and "AndGroup" to get the respective counts */ public Result> groupDistribution(IFeatureTree tree) { - FeatureTreeGroupCounter visitor = new FeatureTreeGroupCounter(); - return Trees.traverse(tree,visitor); + return Trees.traverse(tree, new FeatureTreeGroupCounter()); } - } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index a9e4c633..25abdf57 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -1,24 +1,23 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-base. + * This file is part of FeatJAR-feature-model. * - * base is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * base is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with base. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ - package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; @@ -26,7 +25,6 @@ import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeatureTree; - import java.util.HashMap; import java.util.List; diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index 9b632406..6943042a 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -1,30 +1,28 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-base. + * This file is part of FeatJAR-feature-model. * - * base is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * base is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with base. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ - package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; - import java.util.List; /** @@ -55,7 +53,7 @@ public void reset() { public Result getResult() { float result = 0; if (nodeCount > 0) { - result = (float) childCount /nodeCount; + result = (float) childCount / nodeCount; } return Result.of(result); } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java index a6d37e80..4e2453fe 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java @@ -1,30 +1,28 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-base. + * This file is part of FeatJAR-feature-model. * - * base is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * base is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with base. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ - package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; - import java.util.List; /** @@ -43,7 +41,6 @@ public TraversalAction firstVisit(List> path) { leafCount++; } return TraversalAction.CONTINUE; - } @Override diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index c426aeea..487964ca 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -1,4 +1,25 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.analysis; + import static org.junit.jupiter.api.Assertions.*; import de.featjar.Common; @@ -7,10 +28,8 @@ import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; - -import org.junit.jupiter.api.Test; - import java.util.HashMap; +import org.junit.jupiter.api.Test; public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); @@ -36,6 +55,44 @@ private IFeatureTree generateSmallTree() { return rootTree; } + private IFeatureTree generateFeatureTestTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + + // features + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + rootTree.mutate().toAndGroup(); + + IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); + IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); + + IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); + IFeatureTree childTree2 = rootTree.mutate().addFeatureBelow(childFeature2); + + IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); + IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); + childTree1.mutate().toAlternativeGroup(); + + IFeature childFeature4 = featureModel.mutate().addFeature("Test4"); + childTree1.mutate().addFeatureBelow(childFeature4); + + IFeature childFeature5 = featureModel.mutate().addFeature("Test5"); + IFeatureTree childTree5 = childTree2.mutate().addFeatureBelow(childFeature5); + childTree2.mutate().toOrGroup(); + + IFeature childFeature6 = featureModel.mutate().addFeature("Test6"); + childTree2.mutate().addFeatureBelow(childFeature6); + + IFeature childFeature7 = featureModel.mutate().addFeature("Test7"); + IFeatureTree childTree7 = rootTree.mutate().addFeatureBelow(childFeature7); + childTree7.mutate().makeMandatory(); + + IFeature childFeature8 = featureModel.mutate().addFeature("Test8"); + childTree3.mutate().addFeatureBelow(childFeature8); + + return rootTree; + } + private IFeatureTree generateMediumTree() { // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); @@ -49,15 +106,15 @@ private IFeatureTree generateMediumTree() { IFeature featureGet = featureModel.mutate().addFeature("Get"); IFeatureTree treeGet = treeAPI.mutate().addFeatureBelow(featureGet); - //treeAPI.addChild(treeGet); + // treeAPI.addChild(treeGet); IFeature featurePut = featureModel.mutate().addFeature("Put"); IFeatureTree treePut = treeAPI.mutate().addFeatureBelow(featurePut); - //treeAPI.addChild(treePut); + // treeAPI.addChild(treePut); IFeature featureDelete = featureModel.mutate().addFeature("Delete"); IFeatureTree treeDelete = treeAPI.mutate().addFeatureBelow(featureDelete); - //treeAPI.addChild(treeDelete); + // treeAPI.addChild(treeDelete); treeAPI.mutate().toOrGroup(); IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); @@ -67,19 +124,18 @@ private IFeatureTree generateMediumTree() { IFeature featureOS = featureModel.mutate().addFeature("OS"); IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); treeOS.isMandatory(); - //treeRoot.addChild(treeOS); + // treeRoot.addChild(treeOS); IFeature featureWindows = featureModel.mutate().addFeature("Windows"); IFeatureTree treeWindows = treeOS.mutate().addFeatureBelow(featureWindows); - //treeOS.addChild(treeWindows); + // treeOS.addChild(treeWindows); IFeature featureLinux = featureModel.mutate().addFeature("Linux"); IFeatureTree treeLinux = treeOS.mutate().addFeatureBelow(featureLinux); treeOS.mutate().toAlternativeGroup(); - //treeOS.addChild(treeLinux); + // treeOS.addChild(treeLinux); return treeRoot; - } @Test @@ -108,7 +164,8 @@ void testAvgNumberOfChildren() { @Test void testGroupDistribution() { - HashMap groupCounts = simpleTreeProperties.groupDistribution(smallTree).get(); + HashMap groupCounts = + simpleTreeProperties.groupDistribution(smallTree).get(); assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(3, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); @@ -121,21 +178,27 @@ void smallTest() { IFeatureTree rootTree = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - HashMap groupCounts = simpleTreeProperties.groupDistribution(rootTree).get(); + HashMap groupCounts = + simpleTreeProperties.groupDistribution(rootTree).get(); System.out.println(groupCounts); for (FeatureTree.Group group : rootTree.getChildrenGroups()) { System.out.println(group.isAnd()); } System.out.println(); - } @Test void mediumTest() { - IFeatureTree tree = generateMediumTree(); - HashMap groupCounts = simpleTreeProperties.groupDistribution(tree).get(); + // IFeatureTree tree = generateMediumTree(); + IFeatureTree tree = generateFeatureTestTree(); + HashMap groupCounts = + simpleTreeProperties.groupDistribution(tree).get(); System.out.println(groupCounts); - } + IFeatureTree tree2 = generateMediumTree(); + HashMap groupCounts2 = + simpleTreeProperties.groupDistribution(tree2).get(); + System.out.println(groupCounts2); + } } From a34fea8868849e7a76e27cb400c679bf6c90c443 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 15:35:57 +0200 Subject: [PATCH 054/257] test: every test now checks two trees --- .../analysis/SimpleTreePropertiesTest.java | 82 ++++++++++--------- 1 file changed, 43 insertions(+), 39 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 487964ca..b0c68571 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -25,7 +25,6 @@ import de.featjar.Common; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.FeatureTree; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; import java.util.HashMap; @@ -34,9 +33,12 @@ public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); IFeatureTree smallTree = generateSmallTree(); + IFeatureTree featureTestTree = generateFeatureTestTree(); + IFeatureTree mediumTree = generateMediumTree(); /** - * Creates a tree with a root node that has 1 child, and this child has 2 more children + * Creates a tree with a root node that has 1 child, and this child has 2 more children. The root starts + * an alternative group * @return a small feature tree for testing purposes */ private IFeatureTree generateSmallTree() { @@ -45,16 +47,17 @@ private IFeatureTree generateSmallTree() { featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); rootTree.mutate().toAlternativeGroup(); - IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); + IFeature childFeature1 = featureModel.mutate().addFeature("Root's Child (in AltGroup)"); IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); + IFeature childFeature2 = featureModel.mutate().addFeature("1st Child of Root's Child"); childTree1.mutate().addFeatureBelow(childFeature2); - IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); + IFeature childFeature3 = featureModel.mutate().addFeature("2nd Child of Root's Child"); childTree1.mutate().addFeatureBelow(childFeature3); return rootTree; } + // stolen from a predefined test private IFeatureTree generateFeatureTestTree() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); @@ -77,7 +80,7 @@ private IFeatureTree generateFeatureTestTree() { childTree1.mutate().addFeatureBelow(childFeature4); IFeature childFeature5 = featureModel.mutate().addFeature("Test5"); - IFeatureTree childTree5 = childTree2.mutate().addFeatureBelow(childFeature5); + childTree2.mutate().addFeatureBelow(childFeature5); childTree2.mutate().toOrGroup(); IFeature childFeature6 = featureModel.mutate().addFeature("Test6"); @@ -102,19 +105,13 @@ private IFeatureTree generateMediumTree() { IFeature featureAPI = featureModel.mutate().addFeature("API"); IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); treeAPI.isMandatory(); - // treeRoot.addChild(treeAPI); IFeature featureGet = featureModel.mutate().addFeature("Get"); - IFeatureTree treeGet = treeAPI.mutate().addFeatureBelow(featureGet); - // treeAPI.addChild(treeGet); - + treeAPI.mutate().addFeatureBelow(featureGet); IFeature featurePut = featureModel.mutate().addFeature("Put"); - IFeatureTree treePut = treeAPI.mutate().addFeatureBelow(featurePut); - // treeAPI.addChild(treePut); - + treeAPI.mutate().addFeatureBelow(featurePut); IFeature featureDelete = featureModel.mutate().addFeature("Delete"); - IFeatureTree treeDelete = treeAPI.mutate().addFeatureBelow(featureDelete); - // treeAPI.addChild(treeDelete); + treeAPI.mutate().addFeatureBelow(featureDelete); treeAPI.mutate().toOrGroup(); IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); @@ -124,16 +121,12 @@ private IFeatureTree generateMediumTree() { IFeature featureOS = featureModel.mutate().addFeature("OS"); IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); treeOS.isMandatory(); - // treeRoot.addChild(treeOS); IFeature featureWindows = featureModel.mutate().addFeature("Windows"); - IFeatureTree treeWindows = treeOS.mutate().addFeatureBelow(featureWindows); - // treeOS.addChild(treeWindows); - + treeOS.mutate().addFeatureBelow(featureWindows); IFeature featureLinux = featureModel.mutate().addFeature("Linux"); - IFeatureTree treeLinux = treeOS.mutate().addFeatureBelow(featureLinux); + treeOS.mutate().addFeatureBelow(featureLinux); treeOS.mutate().toAlternativeGroup(); - // treeOS.addChild(treeLinux); return treeRoot; } @@ -142,24 +135,36 @@ private IFeatureTree generateMediumTree() { void testTopFeatures() { int rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); assertEquals(1, rootChildren); + + rootChildren = simpleTreeProperties.topFeatures(mediumTree).get(); + assertEquals(3, rootChildren); } @Test void testLeafFeaturesCounter() { int leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); assertEquals(2, leaves); + + leaves = simpleTreeProperties.leafFeaturesCounter(mediumTree).get(); + assertEquals(6, leaves); } @Test void testTreeDepth() { int depth = simpleTreeProperties.treeDepth(smallTree).get(); assertEquals(3, depth); + + depth = simpleTreeProperties.treeDepth(mediumTree).get(); + assertEquals(3, depth); } @Test void testAvgNumberOfChildren() { float average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); assertEquals(0.75, average); + + average = simpleTreeProperties.avgNumberOfChildren(mediumTree).get(); + assertTrue(0.888 < average && average < 0.889); } @Test @@ -169,29 +174,17 @@ void testGroupDistribution() { assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(3, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); - } - @Test - // to be deleted. This is to find out why everything in our tests has an and group - void smallTest() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree rootTree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - - HashMap groupCounts = - simpleTreeProperties.groupDistribution(rootTree).get(); - System.out.println(groupCounts); - - for (FeatureTree.Group group : rootTree.getChildrenGroups()) { - System.out.println(group.isAnd()); - } - System.out.println(); + groupCounts = simpleTreeProperties.groupDistribution(mediumTree).get(); + assertEquals(1, groupCounts.get("AlternativeGroup")); + assertEquals(7, groupCounts.get("AndGroup")); + assertEquals(1, groupCounts.get("OrGroup")); } + // temp test regarding and groups @Test void mediumTest() { - // IFeatureTree tree = generateMediumTree(); - IFeatureTree tree = generateFeatureTestTree(); + IFeatureTree tree = featureTestTree; HashMap groupCounts = simpleTreeProperties.groupDistribution(tree).get(); System.out.println(groupCounts); @@ -201,4 +194,15 @@ void mediumTest() { simpleTreeProperties.groupDistribution(tree2).get(); System.out.println(groupCounts2); } + + // temp test regarding and groups + @Test + void minimalAndGroupTest() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree tree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + HashMap groupCounts = + simpleTreeProperties.groupDistribution(tree).get(); + System.out.println(groupCounts); + } } From 617eab4aa878da24b56db1b3460550d8a3a7649f Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 15:45:47 +0200 Subject: [PATCH 055/257] test: every test now checks two trees --- .../analysis/SimpleTreePropertiesTest.java | 72 +++---------------- 1 file changed, 11 insertions(+), 61 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index b0c68571..2a2a7372 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -33,7 +33,6 @@ public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); IFeatureTree smallTree = generateSmallTree(); - IFeatureTree featureTestTree = generateFeatureTestTree(); IFeatureTree mediumTree = generateMediumTree(); /** @@ -57,47 +56,13 @@ private IFeatureTree generateSmallTree() { return rootTree; } - // stolen from a predefined test - private IFeatureTree generateFeatureTestTree() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - - // features - IFeatureTree rootTree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - rootTree.mutate().toAndGroup(); - - IFeature childFeature1 = featureModel.mutate().addFeature("Test1"); - IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - - IFeature childFeature2 = featureModel.mutate().addFeature("Test2"); - IFeatureTree childTree2 = rootTree.mutate().addFeatureBelow(childFeature2); - - IFeature childFeature3 = featureModel.mutate().addFeature("Test3"); - IFeatureTree childTree3 = childTree1.mutate().addFeatureBelow(childFeature3); - childTree1.mutate().toAlternativeGroup(); - - IFeature childFeature4 = featureModel.mutate().addFeature("Test4"); - childTree1.mutate().addFeatureBelow(childFeature4); - - IFeature childFeature5 = featureModel.mutate().addFeature("Test5"); - childTree2.mutate().addFeatureBelow(childFeature5); - childTree2.mutate().toOrGroup(); - - IFeature childFeature6 = featureModel.mutate().addFeature("Test6"); - childTree2.mutate().addFeatureBelow(childFeature6); - - IFeature childFeature7 = featureModel.mutate().addFeature("Test7"); - IFeatureTree childTree7 = rootTree.mutate().addFeatureBelow(childFeature7); - childTree7.mutate().makeMandatory(); - - IFeature childFeature8 = featureModel.mutate().addFeature("Test8"); - childTree3.mutate().addFeatureBelow(childFeature8); - - return rootTree; - } - + /** + * Resulting tree has three nodes under the root. API is mandatory and below it is an or-group with the features + * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. + * Transactions is an optional feature below the root. + * @return a medium-sized feature tree for testing purposes. + */ private IFeatureTree generateMediumTree() { - // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); IFeatureTree treeRoot = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); @@ -105,7 +70,6 @@ private IFeatureTree generateMediumTree() { IFeature featureAPI = featureModel.mutate().addFeature("API"); IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); treeAPI.isMandatory(); - IFeature featureGet = featureModel.mutate().addFeature("Get"); treeAPI.mutate().addFeatureBelow(featureGet); IFeature featurePut = featureModel.mutate().addFeature("Put"); @@ -114,20 +78,19 @@ private IFeatureTree generateMediumTree() { treeAPI.mutate().addFeatureBelow(featureDelete); treeAPI.mutate().toOrGroup(); - IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); - IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); - treeTransactions.isOptional(); - IFeature featureOS = featureModel.mutate().addFeature("OS"); IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); treeOS.isMandatory(); - IFeature featureWindows = featureModel.mutate().addFeature("Windows"); treeOS.mutate().addFeatureBelow(featureWindows); IFeature featureLinux = featureModel.mutate().addFeature("Linux"); treeOS.mutate().addFeatureBelow(featureLinux); treeOS.mutate().toAlternativeGroup(); + IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); + IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); + treeTransactions.isOptional(); + return treeRoot; } @@ -182,20 +145,7 @@ void testGroupDistribution() { } // temp test regarding and groups - @Test - void mediumTest() { - IFeatureTree tree = featureTestTree; - HashMap groupCounts = - simpleTreeProperties.groupDistribution(tree).get(); - System.out.println(groupCounts); - - IFeatureTree tree2 = generateMediumTree(); - HashMap groupCounts2 = - simpleTreeProperties.groupDistribution(tree2).get(); - System.out.println(groupCounts2); - } - - // temp test regarding and groups + // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? @Test void minimalAndGroupTest() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); From f387ed4bfdd8e0bad32ac491b38c3c26f127f4a8 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 15:50:14 +0200 Subject: [PATCH 056/257] test, style: removed temp test --- .../model/analysis/SimpleTreePropertiesTest.java | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 2a2a7372..c737e07b 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -143,16 +143,4 @@ void testGroupDistribution() { assertEquals(7, groupCounts.get("AndGroup")); assertEquals(1, groupCounts.get("OrGroup")); } - - // temp test regarding and groups - // why does every regular feature without a .mutate().toXGroup() call become an AndGroup of presumably one? - @Test - void minimalAndGroupTest() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree tree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - HashMap groupCounts = - simpleTreeProperties.groupDistribution(tree).get(); - System.out.println(groupCounts); - } } From 25da967f815193b3559c553d5e89678eb89296e8 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 16:44:18 +0200 Subject: [PATCH 057/257] refactor --- .../model/analysis/SimpleTreeProperties.java | 22 ++----------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index c108a66d..b93916e0 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -1,23 +1,3 @@ -/* - * Copyright (C) 2025 FeatJAR-Development-Team - * - * This file is part of FeatJAR-feature-model. - * - * feature-model is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3.0 of the License, - * or (at your option) any later version. - * - * feature-model is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with feature-model. If not, see . - * - * See for further information. - */ package de.featjar.feature.model.analysis; import de.featjar.base.data.Result; @@ -27,6 +7,7 @@ import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; + import java.util.HashMap; public class SimpleTreeProperties { @@ -72,4 +53,5 @@ public Result avgNumberOfChildren(IFeatureTree tree) { public Result> groupDistribution(IFeatureTree tree) { return Trees.traverse(tree, new FeatureTreeGroupCounter()); } + } From a91e4d0ba73304a15833ce41ba2319847751ca07 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 2 Oct 2025 16:44:52 +0200 Subject: [PATCH 058/257] feat: can gather analysis for CSV output, can export stats to strings --- .../featjar/feature/model/io/CSVExporter.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/io/CSVExporter.java diff --git a/src/main/java/de/featjar/feature/model/io/CSVExporter.java b/src/main/java/de/featjar/feature/model/io/CSVExporter.java new file mode 100644 index 00000000..b87766de --- /dev/null +++ b/src/main/java/de/featjar/feature/model/io/CSVExporter.java @@ -0,0 +1,91 @@ +package de.featjar.feature.model.io; + +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.base.io.IO; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeature; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.SimpleTreeProperties; + +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.stream.Collectors; + +public class CSVExporter { + // should probably pull this from the IO Exporter / Importer + private String csv_delimiter = ";"; + + public IFeatureTree makeTree () { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree rootTree = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + rootTree.mutate().toAlternativeGroup(); + + IFeature childFeature1 = featureModel.mutate().addFeature("Root's Child (in AltGroup)"); + IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); + IFeature childFeature2 = featureModel.mutate().addFeature("1st Child of Root's Child"); + childTree1.mutate().addFeatureBelow(childFeature2); + IFeature childFeature3 = featureModel.mutate().addFeature("2nd Child of Root's Child"); + childTree1.mutate().addFeatureBelow(childFeature3); + + return rootTree; + } + + // change formatting here + private String roundAndCastToString (int numerator, int denominator) { + return String.format("%.2f", (float) numerator / denominator); + } + + private String roundAndCastToString (float number) { + return String.format("%.2f", number); + } + + public LinkedHashMap gatherStatistics (IFeatureTree tree) { + SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); + + int topFeatures = simpleTreeProperties.topFeatures(tree).get(); + int leafFeatures = simpleTreeProperties.leafFeaturesCounter(tree).get(); + int treeDepth = simpleTreeProperties.treeDepth(tree).get(); + + HashMap groupDistribution = simpleTreeProperties.groupDistribution(tree).get(); + int alternativeGroups = groupDistribution.get("AlternativeGroup"); + int orGroups = groupDistribution.get("OrGroup"); + int andGroups = groupDistribution.get("AndGroup"); + int allGroups = alternativeGroups + orGroups + andGroups; + + float avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); + + // linked map to preserve order for now, not sure if needed + LinkedHashMap map = new LinkedHashMap<>(); + map.put("Features Directly Below Root", topFeatures); + map.put("Features That Have No Child Features", leafFeatures); + map.put("Tree Depth", treeDepth); + map.put("Share of Alternative Groups", roundAndCastToString(alternativeGroups, allGroups)); + map.put("Share of Or-Groups", roundAndCastToString(orGroups, allGroups)); + map.put("Share of And-Groups", roundAndCastToString(andGroups, allGroups)); + map.put("Average Number of Children", roundAndCastToString(avgNumberOfChildren)); + + return map; + } + + public static void main(String[] args){ + CSVExporter csvExporter = new CSVExporter(); + IFeatureTree tree = csvExporter.makeTree(); + LinkedHashMap stats = csvExporter.gatherStatistics(tree); + // outputs the stat titles in order + String firstLine = String.join(csvExporter.csv_delimiter, stats.keySet()); + + // outputs the stat values in order + String secondLine = stats.values().stream() + .map(String::valueOf) // safely converts all objects to String, including nulls + .collect(Collectors.joining(csvExporter.csv_delimiter)); + + System.out.println(firstLine); // prints the column names + System.out.println(secondLine); // prints the stats (for the first tree) + + // missing: actual exporter, bonus options + + } + +} From 65f7b0a7b06d56c72bd1c9815c7b126037aec50d Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Sat, 4 Oct 2025 18:36:17 +0200 Subject: [PATCH 059/257] test: added tests for a minimal tree with only 1 node, the root. --- .../analysis/SimpleTreePropertiesTest.java | 51 ++++++++++++++++--- 1 file changed, 43 insertions(+), 8 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index c737e07b..dbad012b 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -32,11 +32,20 @@ public class SimpleTreePropertiesTest extends Common { SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); + IFeatureTree minimalTree = generateMinimalTree(); IFeatureTree smallTree = generateSmallTree(); IFeatureTree mediumTree = generateMediumTree(); /** - * Creates a tree with a root node that has 1 child, and this child has 2 more children. The root starts + * @return bare-bones feature tree with just a root node to test edge cases. + */ + private IFeatureTree generateMinimalTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + return featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + } + + /** + * Creates a feature tree with a root node that has 1 child, and this child has 2 more children. The root starts * an alternative group * @return a small feature tree for testing purposes */ @@ -57,7 +66,7 @@ private IFeatureTree generateSmallTree() { } /** - * Resulting tree has three nodes under the root. API is mandatory and below it is an or-group with the features + * Feature tree with three nodes under the root. API is mandatory and below it is an or-group with the features * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. * Transactions is an optional feature below the root. * @return a medium-sized feature tree for testing purposes. @@ -96,7 +105,12 @@ private IFeatureTree generateMediumTree() { @Test void testTopFeatures() { - int rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); + int rootChildren; + + rootChildren = simpleTreeProperties.topFeatures(minimalTree).get(); + assertEquals(0, rootChildren); + + rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); assertEquals(1, rootChildren); rootChildren = simpleTreeProperties.topFeatures(mediumTree).get(); @@ -105,7 +119,12 @@ void testTopFeatures() { @Test void testLeafFeaturesCounter() { - int leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); + int leaves; + + leaves = simpleTreeProperties.leafFeaturesCounter(minimalTree).get(); + assertEquals(1, leaves); + + leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); assertEquals(2, leaves); leaves = simpleTreeProperties.leafFeaturesCounter(mediumTree).get(); @@ -114,7 +133,12 @@ void testLeafFeaturesCounter() { @Test void testTreeDepth() { - int depth = simpleTreeProperties.treeDepth(smallTree).get(); + int depth; + + depth = simpleTreeProperties.treeDepth(minimalTree).get(); + assertEquals(1, depth); + + depth = simpleTreeProperties.treeDepth(smallTree).get(); assertEquals(3, depth); depth = simpleTreeProperties.treeDepth(mediumTree).get(); @@ -123,7 +147,12 @@ void testTreeDepth() { @Test void testAvgNumberOfChildren() { - float average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); + float average; + + average = simpleTreeProperties.avgNumberOfChildren(minimalTree).get(); + assertEquals(0.0, average); + + average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); assertEquals(0.75, average); average = simpleTreeProperties.avgNumberOfChildren(mediumTree).get(); @@ -132,8 +161,14 @@ void testAvgNumberOfChildren() { @Test void testGroupDistribution() { - HashMap groupCounts = - simpleTreeProperties.groupDistribution(smallTree).get(); + HashMap groupCounts; + + groupCounts = simpleTreeProperties.groupDistribution(minimalTree).get(); + assertEquals(0, groupCounts.get("AlternativeGroup")); + assertEquals(1, groupCounts.get("AndGroup")); + assertEquals(0, groupCounts.get("OrGroup")); + + groupCounts = simpleTreeProperties.groupDistribution(smallTree).get(); assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(3, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); From 52c7d12630bc8f13bc156296080399c07ad2e836 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 10:30:52 +0200 Subject: [PATCH 060/257] refactor --- .../model/io/{ => csv}/CSVExporter.java | 34 +++++++++++++++---- 1 file changed, 28 insertions(+), 6 deletions(-) rename src/main/java/de/featjar/feature/model/io/{ => csv}/CSVExporter.java (79%) diff --git a/src/main/java/de/featjar/feature/model/io/CSVExporter.java b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java similarity index 79% rename from src/main/java/de/featjar/feature/model/io/CSVExporter.java rename to src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java index b87766de..7c823806 100644 --- a/src/main/java/de/featjar/feature/model/io/CSVExporter.java +++ b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java @@ -1,4 +1,4 @@ -package de.featjar.feature.model.io; +package de.featjar.feature.model.io.csv; import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.io.IO; @@ -6,15 +6,22 @@ import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.SimpleTreeProperties; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.nio.charset.Charset; import java.util.HashMap; import java.util.LinkedHashMap; -import java.util.Map; import java.util.stream.Collectors; + +/** + * This is a temp class; the real implementation will be done via IFormat implementation. It will be deleted later. + */ public class CSVExporter { // should probably pull this from the IO Exporter / Importer - private String csv_delimiter = ";"; + public final String DELIMITER = ";"; + public final Charset DEFAULT_CHARSET = IO.DEFAULT_CHARSET; public IFeatureTree makeTree () { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); @@ -57,6 +64,7 @@ public LinkedHashMap gatherStatistics (IFeatureTree tree) { float avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); // linked map to preserve order for now, not sure if needed + // todo: decide whether to make this a variable map, or a ready-to-write map LinkedHashMap map = new LinkedHashMap<>(); map.put("Features Directly Below Root", topFeatures); map.put("Features That Have No Child Features", leafFeatures); @@ -69,22 +77,36 @@ public LinkedHashMap gatherStatistics (IFeatureTree tree) { return map; } + public void export(String[] csvStrings) { + Path path = Paths.get("C:\\Users\\bentu\\Desktop\\myfile.csv"); + + /* + IO.write( + String.join("\n", csvStrings), + path, + DEFAULT_CHARSET + ); + + */ + + } + public static void main(String[] args){ CSVExporter csvExporter = new CSVExporter(); IFeatureTree tree = csvExporter.makeTree(); LinkedHashMap stats = csvExporter.gatherStatistics(tree); // outputs the stat titles in order - String firstLine = String.join(csvExporter.csv_delimiter, stats.keySet()); + String firstLine = String.join(csvExporter.DELIMITER, stats.keySet()); // outputs the stat values in order String secondLine = stats.values().stream() .map(String::valueOf) // safely converts all objects to String, including nulls - .collect(Collectors.joining(csvExporter.csv_delimiter)); + .collect(Collectors.joining(csvExporter.DELIMITER)); System.out.println(firstLine); // prints the column names System.out.println(secondLine); // prints the stats (for the first tree) - // missing: actual exporter, bonus options + // todo iformat, xmlformat } From 02999dd0757d25f3fe9aa8ac98a9a9e53834d891 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 14:06:26 +0200 Subject: [PATCH 061/257] feat: now also counts other potential groups. --- .../model/analysis/visitor/FeatureTreeGroupCounter.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index 25abdf57..6a0fc52b 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -34,7 +34,7 @@ * @author Sebastian Krieter */ public class FeatureTreeGroupCounter implements ITreeVisitor, HashMap> { - int altCounter = 0, orCounter = 0, andCounter = 0; + int altCounter = 0, orCounter = 0, andCounter = 0, otherCounter = 0; @Override public TraversalAction firstVisit(List> path) { @@ -47,6 +47,8 @@ public TraversalAction firstVisit(List> path) { orCounter++; } else if (group.isAnd()) { andCounter++; + } else { + otherCounter++; } } @@ -58,6 +60,7 @@ public void reset() { altCounter = 0; orCounter = 0; andCounter = 0; + otherCounter = 0; } @Override @@ -66,6 +69,7 @@ public Result> getResult() { countedGroups.put("AlternativeGroup", altCounter); countedGroups.put("OrGroup", orCounter); countedGroups.put("AndGroup", andCounter); + countedGroups.put("OtherGroup", otherCounter); return Result.of(countedGroups); } } From 6e7a46dfc67cdbf8f9436ca2ad1779eab6c5cfb2 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 14:07:23 +0200 Subject: [PATCH 062/257] refactor: now uses Double instead of Float as per supervisor request --- .../model/analysis/SimpleTreeProperties.java | 13 ++++++------- .../analysis/visitor/TreeAvgChildrenCounter.java | 8 ++++---- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index b93916e0..b570a33c 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -14,17 +14,16 @@ public class SimpleTreeProperties { /** * @param tree: feature tree - * @return number of features directly below the root of this subtree. + * {@return number of features directly below the root of this subtree.} */ public Result topFeatures(IFeatureTree tree) { - // int childrenCount = tree.getRoot().getChildrenCount(); // if we should find a subtree's root automatically - int childrenCount = tree.getChildrenCount(); + int childrenCount = tree.getChildrenCount(); // when doing computations: input the feature model return Result.of(childrenCount); } /** * @param tree: feature tree - * @return the number of features that have no child features + * {@return the number of features that have no child features} */ public Result leafFeaturesCounter(IFeatureTree tree) { return Trees.traverse(tree, new TreeLeafCounter()); @@ -32,7 +31,7 @@ public Result leafFeaturesCounter(IFeatureTree tree) { /** * @param tree: feature tree - * @return tree depth, meaning the longest path from this subtree's root to its most distant leaf node + * {@return tree depth, meaning the longest path from this subtree's root to its most distant leaf node} */ public Result treeDepth(IFeatureTree tree) { return Trees.traverse(tree, new TreeDepthCounter()); @@ -40,9 +39,9 @@ public Result treeDepth(IFeatureTree tree) { /** * @param tree: feature tree - * @return average number of children that each node in the tree has, rounded to integer. + * {@return average number of children that each node in the tree has, rounded to integer.} */ - public Result avgNumberOfChildren(IFeatureTree tree) { + public Result avgNumberOfChildren(IFeatureTree tree) { return Trees.traverse(tree, new TreeAvgChildrenCounter()); } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index 6943042a..0b716b4a 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -31,7 +31,7 @@ * * @author Sebastian Krieter */ -public class TreeAvgChildrenCounter implements ITreeVisitor, Float> { +public class TreeAvgChildrenCounter implements ITreeVisitor, Double> { private int nodeCount = 0; private int childCount = 0; @@ -50,10 +50,10 @@ public void reset() { } @Override - public Result getResult() { - float result = 0; + public Result getResult() { + double result = 0; if (nodeCount > 0) { - result = (float) childCount / nodeCount; + result = (double) childCount / nodeCount; } return Result.of(result); } From d9e55db5c72355b108577cca6b9c6794e088e7ee Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 14:18:23 +0200 Subject: [PATCH 063/257] refactor: now uses Double instead of Float as per supervisor request --- .../de/featjar/feature/model/analysis/SimpleTreeProperties.java | 1 - .../feature/model/analysis/SimpleTreePropertiesTest.java | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java index b570a33c..3452632c 100644 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java @@ -11,7 +11,6 @@ import java.util.HashMap; public class SimpleTreeProperties { - /** * @param tree: feature tree * {@return number of features directly below the root of this subtree.} diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index dbad012b..7764a559 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -147,7 +147,7 @@ void testTreeDepth() { @Test void testAvgNumberOfChildren() { - float average; + double average; average = simpleTreeProperties.avgNumberOfChildren(minimalTree).get(); assertEquals(0.0, average); From b84a170b3039bd1a533ce52990ad5564ccb72acc Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 14:38:12 +0200 Subject: [PATCH 064/257] refactor: now uses Double instead of Float as per supervisor request --- .../java/de/featjar/feature/model/io/csv/CSVExporter.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java index 7c823806..57a0a1da 100644 --- a/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java +++ b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java @@ -44,7 +44,7 @@ private String roundAndCastToString (int numerator, int denominator) { return String.format("%.2f", (float) numerator / denominator); } - private String roundAndCastToString (float number) { + private String roundAndCastToString (double number) { return String.format("%.2f", number); } @@ -61,7 +61,7 @@ public LinkedHashMap gatherStatistics (IFeatureTree tree) { int andGroups = groupDistribution.get("AndGroup"); int allGroups = alternativeGroups + orGroups + andGroups; - float avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); + double avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); // linked map to preserve order for now, not sure if needed // todo: decide whether to make this a variable map, or a ready-to-write map From 6fe127e534aee1c9a2f655728c866a94c01fd821 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 6 Oct 2025 15:01:27 +0200 Subject: [PATCH 065/257] refactor: switched all calculations to the Computations wrapper. --- ...ComputeFeatureAverageNumberOfChildren.java | 50 ++++++++ .../ComputeFeatureFeaturesCounter.java | 50 ++++++++ .../ComputeFeatureGroupDistribution.java | 51 ++++++++ .../analysis/ComputeFeatureTopFeatures.java | 48 ++++++++ .../analysis/ComputeFeatureTreeDepth.java | 50 ++++++++ .../model/analysis/SimpleTreeProperties.java | 55 --------- .../feature/model/io/csv/CSVExporter.java | 113 ------------------ .../analysis/SimpleTreePropertiesTest.java | 53 +++++--- 8 files changed, 286 insertions(+), 184 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java delete mode 100644 src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java delete mode 100644 src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java new file mode 100644 index 00000000..4b4ff5ae --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; +import java.util.List; + +/** + * Calculates the number of features that have no child features. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureAverageNumberOfChildren extends AComputation { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureAverageNumberOfChildren(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Trees.traverse(tree, new TreeAvgChildrenCounter()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java new file mode 100644 index 00000000..12b466e8 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; +import java.util.List; + +/** + * Calculates the number of features that have no child features. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureFeaturesCounter extends AComputation { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureFeaturesCounter(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Trees.traverse(tree, new TreeLeafCounter()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java new file mode 100644 index 00000000..3370b175 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; +import java.util.HashMap; +import java.util.List; + +/** + * Calculates the number of features that have no child features. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureGroupDistribution extends AComputation> { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureGroupDistribution(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result> compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Trees.traverse(tree, new FeatureTreeGroupCounter()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java new file mode 100644 index 00000000..348e6148 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.feature.model.IFeatureTree; +import java.util.List; + +/** + * Calculates the number of features directly below the root of this subtree. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureTopFeatures extends AComputation { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureTopFeatures(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Result.of(tree.getChildrenCount()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java new file mode 100644 index 00000000..6dee4b48 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.base.tree.visitor.TreeDepthCounter; +import de.featjar.feature.model.IFeatureTree; +import java.util.List; + +/** + * Calculates the number of features that have no child features. + * + * @author Benjamin von Holt + */ +public class ComputeFeatureTreeDepth extends AComputation { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureTreeDepth(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Trees.traverse(tree, new TreeDepthCounter()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java b/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java deleted file mode 100644 index 3452632c..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/SimpleTreeProperties.java +++ /dev/null @@ -1,55 +0,0 @@ -package de.featjar.feature.model.analysis; - -import de.featjar.base.data.Result; -import de.featjar.base.tree.Trees; -import de.featjar.base.tree.visitor.TreeDepthCounter; -import de.featjar.feature.model.*; -import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; -import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; -import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; - -import java.util.HashMap; - -public class SimpleTreeProperties { - /** - * @param tree: feature tree - * {@return number of features directly below the root of this subtree.} - */ - public Result topFeatures(IFeatureTree tree) { - int childrenCount = tree.getChildrenCount(); // when doing computations: input the feature model - return Result.of(childrenCount); - } - - /** - * @param tree: feature tree - * {@return the number of features that have no child features} - */ - public Result leafFeaturesCounter(IFeatureTree tree) { - return Trees.traverse(tree, new TreeLeafCounter()); - } - - /** - * @param tree: feature tree - * {@return tree depth, meaning the longest path from this subtree's root to its most distant leaf node} - */ - public Result treeDepth(IFeatureTree tree) { - return Trees.traverse(tree, new TreeDepthCounter()); - } - - /** - * @param tree: feature tree - * {@return average number of children that each node in the tree has, rounded to integer.} - */ - public Result avgNumberOfChildren(IFeatureTree tree) { - return Trees.traverse(tree, new TreeAvgChildrenCounter()); - } - - /** Counts the number of different groups in this tree. - * @param tree: feature tree - * @return hashmap with the String keys "AlternativeGroup", "OrGroup" and "AndGroup" to get the respective counts - */ - public Result> groupDistribution(IFeatureTree tree) { - return Trees.traverse(tree, new FeatureTreeGroupCounter()); - } - -} diff --git a/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java b/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java deleted file mode 100644 index 57a0a1da..00000000 --- a/src/main/java/de/featjar/feature/model/io/csv/CSVExporter.java +++ /dev/null @@ -1,113 +0,0 @@ -package de.featjar.feature.model.io.csv; - -import de.featjar.base.data.identifier.Identifiers; -import de.featjar.base.io.IO; -import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.IFeature; -import de.featjar.feature.model.IFeatureTree; -import de.featjar.feature.model.analysis.SimpleTreeProperties; -import java.nio.file.Path; -import java.nio.file.Paths; - -import java.nio.charset.Charset; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.stream.Collectors; - - -/** - * This is a temp class; the real implementation will be done via IFormat implementation. It will be deleted later. - */ -public class CSVExporter { - // should probably pull this from the IO Exporter / Importer - public final String DELIMITER = ";"; - public final Charset DEFAULT_CHARSET = IO.DEFAULT_CHARSET; - - public IFeatureTree makeTree () { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree rootTree = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); - rootTree.mutate().toAlternativeGroup(); - - IFeature childFeature1 = featureModel.mutate().addFeature("Root's Child (in AltGroup)"); - IFeatureTree childTree1 = rootTree.mutate().addFeatureBelow(childFeature1); - IFeature childFeature2 = featureModel.mutate().addFeature("1st Child of Root's Child"); - childTree1.mutate().addFeatureBelow(childFeature2); - IFeature childFeature3 = featureModel.mutate().addFeature("2nd Child of Root's Child"); - childTree1.mutate().addFeatureBelow(childFeature3); - - return rootTree; - } - - // change formatting here - private String roundAndCastToString (int numerator, int denominator) { - return String.format("%.2f", (float) numerator / denominator); - } - - private String roundAndCastToString (double number) { - return String.format("%.2f", number); - } - - public LinkedHashMap gatherStatistics (IFeatureTree tree) { - SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); - - int topFeatures = simpleTreeProperties.topFeatures(tree).get(); - int leafFeatures = simpleTreeProperties.leafFeaturesCounter(tree).get(); - int treeDepth = simpleTreeProperties.treeDepth(tree).get(); - - HashMap groupDistribution = simpleTreeProperties.groupDistribution(tree).get(); - int alternativeGroups = groupDistribution.get("AlternativeGroup"); - int orGroups = groupDistribution.get("OrGroup"); - int andGroups = groupDistribution.get("AndGroup"); - int allGroups = alternativeGroups + orGroups + andGroups; - - double avgNumberOfChildren = simpleTreeProperties.avgNumberOfChildren(tree).get(); - - // linked map to preserve order for now, not sure if needed - // todo: decide whether to make this a variable map, or a ready-to-write map - LinkedHashMap map = new LinkedHashMap<>(); - map.put("Features Directly Below Root", topFeatures); - map.put("Features That Have No Child Features", leafFeatures); - map.put("Tree Depth", treeDepth); - map.put("Share of Alternative Groups", roundAndCastToString(alternativeGroups, allGroups)); - map.put("Share of Or-Groups", roundAndCastToString(orGroups, allGroups)); - map.put("Share of And-Groups", roundAndCastToString(andGroups, allGroups)); - map.put("Average Number of Children", roundAndCastToString(avgNumberOfChildren)); - - return map; - } - - public void export(String[] csvStrings) { - Path path = Paths.get("C:\\Users\\bentu\\Desktop\\myfile.csv"); - - /* - IO.write( - String.join("\n", csvStrings), - path, - DEFAULT_CHARSET - ); - - */ - - } - - public static void main(String[] args){ - CSVExporter csvExporter = new CSVExporter(); - IFeatureTree tree = csvExporter.makeTree(); - LinkedHashMap stats = csvExporter.gatherStatistics(tree); - // outputs the stat titles in order - String firstLine = String.join(csvExporter.DELIMITER, stats.keySet()); - - // outputs the stat values in order - String secondLine = stats.values().stream() - .map(String::valueOf) // safely converts all objects to String, including nulls - .collect(Collectors.joining(csvExporter.DELIMITER)); - - System.out.println(firstLine); // prints the column names - System.out.println(secondLine); // prints the stats (for the first tree) - - // todo iformat, xmlformat - - } - -} diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 7764a559..39e9814e 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; import de.featjar.Common; +import de.featjar.base.computation.Computations; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeature; @@ -31,7 +32,6 @@ import org.junit.jupiter.api.Test; public class SimpleTreePropertiesTest extends Common { - SimpleTreeProperties simpleTreeProperties = new SimpleTreeProperties(); IFeatureTree minimalTree = generateMinimalTree(); IFeatureTree smallTree = generateSmallTree(); IFeatureTree mediumTree = generateMediumTree(); @@ -107,13 +107,16 @@ private IFeatureTree generateMediumTree() { void testTopFeatures() { int rootChildren; - rootChildren = simpleTreeProperties.topFeatures(minimalTree).get(); + rootChildren = + Computations.of(minimalTree).map(ComputeFeatureTopFeatures::new).compute(); assertEquals(0, rootChildren); - rootChildren = simpleTreeProperties.topFeatures(smallTree).get(); + rootChildren = + Computations.of(smallTree).map(ComputeFeatureTopFeatures::new).compute(); assertEquals(1, rootChildren); - rootChildren = simpleTreeProperties.topFeatures(mediumTree).get(); + rootChildren = + Computations.of(mediumTree).map(ComputeFeatureTopFeatures::new).compute(); assertEquals(3, rootChildren); } @@ -121,13 +124,19 @@ void testTopFeatures() { void testLeafFeaturesCounter() { int leaves; - leaves = simpleTreeProperties.leafFeaturesCounter(minimalTree).get(); + leaves = Computations.of(minimalTree) + .map(ComputeFeatureFeaturesCounter::new) + .compute(); assertEquals(1, leaves); - leaves = simpleTreeProperties.leafFeaturesCounter(smallTree).get(); + leaves = Computations.of(smallTree) + .map(ComputeFeatureFeaturesCounter::new) + .compute(); assertEquals(2, leaves); - leaves = simpleTreeProperties.leafFeaturesCounter(mediumTree).get(); + leaves = Computations.of(mediumTree) + .map(ComputeFeatureFeaturesCounter::new) + .compute(); assertEquals(6, leaves); } @@ -135,13 +144,13 @@ void testLeafFeaturesCounter() { void testTreeDepth() { int depth; - depth = simpleTreeProperties.treeDepth(minimalTree).get(); + depth = Computations.of(minimalTree).map(ComputeFeatureTreeDepth::new).compute(); assertEquals(1, depth); - depth = simpleTreeProperties.treeDepth(smallTree).get(); + depth = Computations.of(smallTree).map(ComputeFeatureTreeDepth::new).compute(); assertEquals(3, depth); - depth = simpleTreeProperties.treeDepth(mediumTree).get(); + depth = Computations.of(mediumTree).map(ComputeFeatureTreeDepth::new).compute(); assertEquals(3, depth); } @@ -149,13 +158,19 @@ void testTreeDepth() { void testAvgNumberOfChildren() { double average; - average = simpleTreeProperties.avgNumberOfChildren(minimalTree).get(); + average = Computations.of(minimalTree) + .map(ComputeFeatureAverageNumberOfChildren::new) + .compute(); assertEquals(0.0, average); - average = simpleTreeProperties.avgNumberOfChildren(smallTree).get(); + average = Computations.of(smallTree) + .map(ComputeFeatureAverageNumberOfChildren::new) + .compute(); assertEquals(0.75, average); - average = simpleTreeProperties.avgNumberOfChildren(mediumTree).get(); + average = Computations.of(mediumTree) + .map(ComputeFeatureAverageNumberOfChildren::new) + .compute(); assertTrue(0.888 < average && average < 0.889); } @@ -163,17 +178,23 @@ void testAvgNumberOfChildren() { void testGroupDistribution() { HashMap groupCounts; - groupCounts = simpleTreeProperties.groupDistribution(minimalTree).get(); + groupCounts = Computations.of(minimalTree) + .map(ComputeFeatureGroupDistribution::new) + .compute(); assertEquals(0, groupCounts.get("AlternativeGroup")); assertEquals(1, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); - groupCounts = simpleTreeProperties.groupDistribution(smallTree).get(); + groupCounts = Computations.of(smallTree) + .map(ComputeFeatureGroupDistribution::new) + .compute(); assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(3, groupCounts.get("AndGroup")); assertEquals(0, groupCounts.get("OrGroup")); - groupCounts = simpleTreeProperties.groupDistribution(mediumTree).get(); + groupCounts = Computations.of(mediumTree) + .map(ComputeFeatureGroupDistribution::new) + .compute(); assertEquals(1, groupCounts.get("AlternativeGroup")); assertEquals(7, groupCounts.get("AndGroup")); assertEquals(1, groupCounts.get("OrGroup")); From 6d963cc42aaa08d94a4d61ee6128cbc772f147bb Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Mon, 6 Oct 2025 16:51:36 +0200 Subject: [PATCH 066/257] Feat: connecting of functionality of other teams WIP --- .../feature/model/cli/PrintStatistics.java | 87 ++++++++++++++++--- 1 file changed, 74 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 3b479857..533df0ec 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -24,12 +24,27 @@ import de.featjar.base.cli.ACommand; import de.featjar.base.cli.Option; import de.featjar.base.cli.OptionList; +import de.featjar.base.computation.Computations; +import de.featjar.base.computation.IComputation; import de.featjar.base.data.Result; import de.featjar.base.io.IO; +import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeatureModel; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.ComputeFeatureAverageNumberOfChildren; +import de.featjar.feature.model.analysis.ComputeFeatureTopFeatures; +import de.featjar.feature.model.computation.ComputeAtomsCount; +import de.featjar.feature.model.computation.ComputeAverageConstraint; +import de.featjar.feature.model.computation.ComputeFeatureDensity; +import de.featjar.feature.model.computation.ComputeOperatorDistribution; import de.featjar.feature.model.io.FeatureModelFormats; + +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.nio.file.Path; import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -55,7 +70,9 @@ enum AnalysesScope { public static final Option PRETTY_PRINT = Option.newFlag("pretty").setDescription("Pretty prints the numbers"); - private HashMap data; + private LinkedHashMap data; + private FeatureModel model; + private IFeatureModel imodel; @Override public int run(OptionList optionParser) { @@ -68,13 +85,14 @@ public int run(OptionList optionParser) { // opening input model Path path = optionParser.getResult(INPUT_OPTION).orElseThrow(); Result load = IO.load(path, FeatureModelFormats.getInstance()); - IFeatureModel model = load.orElseThrow(); + imodel = load.orElseThrow(); + model = (FeatureModel) imodel; // collecting statistics of the model, checking if scope is specified if (optionParser.getResult(ANALYSES_SCOPE).isPresent()) { - data = collectStats(model, optionParser.get(ANALYSES_SCOPE)); + data = collectStats(optionParser.get(ANALYSES_SCOPE)); } else { - data = collectStats(model, AnalysesScope.ALL); + data = collectStats(AnalysesScope.ALL); } // if output path is specified, write statistics to file @@ -124,31 +142,74 @@ private void writeTo(Path path, String type) { } } - private HashMap collectStats(IFeatureModel model, AnalysesScope scope) { + private LinkedHashMap collectStats(AnalysesScope scope) { - HashMap data = new HashMap(); + data = new LinkedHashMap(); + if (scope == AnalysesScope.ALL || scope == AnalysesScope.CONSTRAINT_RELATED) { - - // For Example data.put(model.getConstraintInfo()) - + //Fetching constraint related statistics + data.put("Number of Atoms", (float)Computations.of(model).map(ComputeAtomsCount::new).compute()); + data.put("Feature Density", (float)Computations.of(model).map(ComputeFeatureDensity::new).compute()); + data.put("Average Constraints", (float)Computations.of(model).map(ComputeAverageConstraint::new).compute()); + + HashMap computational_opDensity = Computations.of(model) + .map(ComputeOperatorDistribution::new) + .compute(); + data.put("Operator Density AND", (float)computational_opDensity.get("And")); + data.put("Operator Density Or", (float)computational_opDensity.get("Or")); + data.put("Operator Density Not", (float)computational_opDensity.get("Not")); + data.put("Operator Density Implies", (float)computational_opDensity.get("Implies")); } + if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { - // For Example model.getTreeDepth() - + //Fetching tree related statistics + + List trees = model.getRoots(); + float value; + String treePrefix; + + for (int i = 0; i < trees.size(); i++) { + treePrefix = "[Tree "+(i+1) + "] "; + + IFeatureTree tree = trees.get(i); + + // avg num of children + double average = Computations.of(tree) + .map(ComputeFeatureAverageNumberOfChildren::new) + .compute(); + value = (float) average; + data.put(treePrefix + "Average Number of Childen", value); + + // num of top features + int topFeatures = Computations.of(tree).map(ComputeFeatureTopFeatures::new).compute(); + data.put(treePrefix + "Number of Top Features", (float) topFeatures); + + // tree depth + + } + + + + + // Tree depth + // Number of leaf features + // Group distribution + } // dummy values, will be handled by functions of other teams + /* data.put("numOfTopFeatures", 3); data.put("numOfLeafFeatures", 12); data.put("treeDepth", 3); data.put("avgNumOfChildren", 3); data.put("numInOrGroups", 7); data.put("numInAltGroups", 5); - data.put("numOfAtoms", 8); data.put("avgNumOfAtomsPerConstraints", 4); + */ return data; } @@ -161,7 +222,7 @@ private StringBuilder buildPrettyStats() { StringBuilder outputString = new StringBuilder(); for (Map.Entry entry : data.entrySet()) { - outputString.append(String.format("%-30s : %s%n", entry.getKey(), entry.getValue())); + outputString.append(String.format("%-40s : %s%n", entry.getKey(), entry.getValue())); } return outputString; } From be7259da4111fabf28c3988a1bf74910951f4af2 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 7 Oct 2025 09:30:33 +0200 Subject: [PATCH 067/257] doc: indicated return values of hashmap --- .../feature/model/analysis/ComputeFeatureGroupDistribution.java | 1 + .../feature/model/analysis/visitor/FeatureTreeGroupCounter.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java index 3370b175..e957d57d 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java @@ -33,6 +33,7 @@ /** * Calculates the number of features that have no child features. + * Hashmap returns, in order: AlternativeGroup, OrGroup, AndGroup, OtherGroup. * * @author Benjamin von Holt */ diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index 6a0fc52b..0b62d699 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -29,7 +29,7 @@ import java.util.List; /** - * Counts the share of groups found in the given feature tree, in order: Or, And, Alternate. + * Counts the share of groups found in the given feature tree, in order: AlternativeGroup, OrGroup, AndGroup, OtherGroup. * * @author Sebastian Krieter */ From 49e0fb85563123aec45c6524b1da0a2cac2c6b26 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 7 Oct 2025 10:16:39 +0200 Subject: [PATCH 068/257] feat: connecting to functionality of other teams --- .../feature/model/cli/PrintStatistics.java | 159 ++++++++++-------- 1 file changed, 91 insertions(+), 68 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 533df0ec..80f068dd 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -25,22 +25,17 @@ import de.featjar.base.cli.Option; import de.featjar.base.cli.OptionList; import de.featjar.base.computation.Computations; -import de.featjar.base.computation.IComputation; import de.featjar.base.data.Result; import de.featjar.base.io.IO; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; -import de.featjar.feature.model.analysis.ComputeFeatureAverageNumberOfChildren; -import de.featjar.feature.model.analysis.ComputeFeatureTopFeatures; +import de.featjar.feature.model.analysis.*; import de.featjar.feature.model.computation.ComputeAtomsCount; import de.featjar.feature.model.computation.ComputeAverageConstraint; import de.featjar.feature.model.computation.ComputeFeatureDensity; import de.featjar.feature.model.computation.ComputeOperatorDistribution; import de.featjar.feature.model.io.FeatureModelFormats; - -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashMap; @@ -70,7 +65,7 @@ enum AnalysesScope { public static final Option PRETTY_PRINT = Option.newFlag("pretty").setDescription("Pretty prints the numbers"); - private LinkedHashMap data; + private LinkedHashMap data; private FeatureModel model; private IFeatureModel imodel; @@ -142,75 +137,91 @@ private void writeTo(Path path, String type) { } } - private LinkedHashMap collectStats(AnalysesScope scope) { + private LinkedHashMap collectStats(AnalysesScope scope) { - data = new LinkedHashMap(); - + data = new LinkedHashMap(); if (scope == AnalysesScope.ALL || scope == AnalysesScope.CONSTRAINT_RELATED) { - //Fetching constraint related statistics - data.put("Number of Atoms", (float)Computations.of(model).map(ComputeAtomsCount::new).compute()); - data.put("Feature Density", (float)Computations.of(model).map(ComputeFeatureDensity::new).compute()); - data.put("Average Constraints", (float)Computations.of(model).map(ComputeAverageConstraint::new).compute()); - - HashMap computational_opDensity = Computations.of(model) - .map(ComputeOperatorDistribution::new) - .compute(); - data.put("Operator Density AND", (float)computational_opDensity.get("And")); - data.put("Operator Density Or", (float)computational_opDensity.get("Or")); - data.put("Operator Density Not", (float)computational_opDensity.get("Not")); - data.put("Operator Density Implies", (float)computational_opDensity.get("Implies")); + // Fetching constraint related statistics + data.put( + "Number of Atoms", + Computations.of(model).map(ComputeAtomsCount::new).compute()); + data.put( + "Feature Density", + Computations.of(model).map(ComputeFeatureDensity::new).compute()); + data.put( + "Average Constraints", + Computations.of(model).map(ComputeAverageConstraint::new).compute()); + + HashMap computational_opDensity = + Computations.of(model).map(ComputeOperatorDistribution::new).compute(); + + /* + for(int i = 0; i < computational_opDensity.size(); i++) { + + }*/ + + data.put("Operator Distribution", computational_opDensity); + + /* + data.put("Operator Density AND", computational_opDensity.get("And")); + data.put("Operator Density Or", computational_opDensity.get("Or")); + data.put("Operator Density Not", computational_opDensity.get("Not")); + data.put("Operator Density Implies", computational_opDensity.get("Implies")); + */ } - if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { - //Fetching tree related statistics - - List trees = model.getRoots(); - float value; - String treePrefix; - - for (int i = 0; i < trees.size(); i++) { - treePrefix = "[Tree "+(i+1) + "] "; - - IFeatureTree tree = trees.get(i); - - // avg num of children - double average = Computations.of(tree) - .map(ComputeFeatureAverageNumberOfChildren::new) - .compute(); - value = (float) average; - data.put(treePrefix + "Average Number of Childen", value); - - // num of top features - int topFeatures = Computations.of(tree).map(ComputeFeatureTopFeatures::new).compute(); - data.put(treePrefix + "Number of Top Features", (float) topFeatures); - - // tree depth - - } - - - - - // Tree depth - // Number of leaf features - // Group distribution - + // Fetching tree related statistics + + model.mutate().addFeatureTreeRoot(model.mutate().addFeature("Second Tree Root")); + + List trees = model.getRoots(); + String treePrefix; + + for (int i = 0; i < trees.size(); i++) { + treePrefix = "[Tree " + (i + 1) + "] "; + // treePrefix = "\t"; + // data.put("[Tree "+(i+1) + "]", ""); + + IFeatureTree tree = trees.get(i); + + // avg num of children + data.put( + treePrefix + "Average Number of Childen", + Computations.of(tree) + .map(ComputeFeatureAverageNumberOfChildren::new) + .compute()); + + // num of top features + data.put( + treePrefix + "Number of Top Features", + Computations.of(tree) + .map(ComputeFeatureTopFeatures::new) + .compute()); + + // num of leaf features + data.put( + treePrefix + "Number of Leaf Features", + Computations.of(tree) + .map(ComputeFeatureFeaturesCounter::new) + .compute()); + + // tree depth + data.put( + treePrefix + "Tree Depth", + Computations.of(tree).map(ComputeFeatureTreeDepth::new).compute()); + + // group distribution + data.put( + treePrefix + "Group Distribution", + Computations.of(tree) + .map(ComputeFeatureGroupDistribution::new) + .compute()); + } } - // dummy values, will be handled by functions of other teams - /* - data.put("numOfTopFeatures", 3); - data.put("numOfLeafFeatures", 12); - data.put("treeDepth", 3); - data.put("avgNumOfChildren", 3); - data.put("numInOrGroups", 7); - data.put("numInAltGroups", 5); - data.put("avgNumOfAtomsPerConstraints", 4); - */ - return data; } @@ -221,8 +232,20 @@ public void printStatsPretty() { private StringBuilder buildPrettyStats() { StringBuilder outputString = new StringBuilder(); + // if(isInstance(Map.Entry, LinkedHashMap) ) + for (Map.Entry entry : data.entrySet()) { - outputString.append(String.format("%-40s : %s%n", entry.getKey(), entry.getValue())); + + if (entry.getValue() instanceof HashMap) { + /* + HashMap myHashMap = (HashMap) entry.getValue(); + for(HashMap single : entry.getValue() { + outputString.append(String.format("%-40s : %s%n", single.getKey(), single.getValue())); + } + */ + } else { + outputString.append(String.format("%-40s : %s%n", entry.getKey(), entry.getValue())); + } } return outputString; } From a490e890386651a9ae930e0359b72dcfda763d9a Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 7 Oct 2025 10:19:03 +0200 Subject: [PATCH 069/257] feat: connecting to functionality of other teams WIP --- .../java/de/featjar/feature/model/analysis/AtomsCount.java | 2 +- .../feature/model/analysis/OperatorDistribution.java | 5 ++--- .../feature/model/computation/ComputeAtomsCount.java | 7 +++---- 3 files changed, 6 insertions(+), 8 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java index cf37c9b0..adb981c9 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java +++ b/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java @@ -44,7 +44,7 @@ public class AtomsCount implements ITreeVisitor, Integer> { private boolean countBoolean = true; /** - * + * * @param countVariables decide if Atoms of type variable should be counted * @param countConstants decide if Atoms of type constants should be counted * @param countBoolean decide if Atoms of type True or False should be counted diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java index 8f97caeb..bc1dceff 100644 --- a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java @@ -25,7 +25,6 @@ import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.formula.structure.connective.IConnective; import de.featjar.formula.structure.connective.Reference; - import java.util.HashMap; import java.util.List; @@ -37,13 +36,13 @@ * @author Florian Beese */ public class OperatorDistribution implements ITreeVisitor, HashMap> { - // Saves the count of each operator, where each key is the name of the class of the operator + // Saves the count of each operator, where each key is the name of the class of the operator HashMap operatorCount = new HashMap(); @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); - if (node instanceof IConnective && ! (node instanceof Reference)) { + if (node instanceof IConnective && !(node instanceof Reference)) { String nodeKey = node.getClass().getSimpleName(); if (!operatorCount.containsKey(nodeKey)) { operatorCount.put(nodeKey, 1); diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index 44bf884f..db0c4d57 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -45,14 +45,13 @@ * */ public class ComputeAtomsCount extends AComputation { protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); - //COUNTCONSTANTS decide if Atoms of type constants should be counted + // COUNTCONSTANTS decide if Atoms of type constants should be counted protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); - //COUNTVARIABLES decide if Atoms of type variable should be counted + // COUNTVARIABLES decide if Atoms of type variable should be counted protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); - //COUNTBOOLEAN decide if Atoms of type True or False should be counted + // COUNTBOOLEAN decide if Atoms of type True or False should be counted protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); - public ComputeAtomsCount(IComputation featureModel) { super( featureModel, From d44ab8a4f3a9df6ac505d5295779eada0b4e17f1 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 7 Oct 2025 10:23:50 +0200 Subject: [PATCH 070/257] fix: attempted fix for appending strings of nested map --- .../featjar/feature/model/cli/PrintStatistics.java | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 80f068dd..5ba02860 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -236,13 +236,11 @@ private StringBuilder buildPrettyStats() { for (Map.Entry entry : data.entrySet()) { - if (entry.getValue() instanceof HashMap) { - /* - HashMap myHashMap = (HashMap) entry.getValue(); - for(HashMap single : entry.getValue() { - outputString.append(String.format("%-40s : %s%n", single.getKey(), single.getValue())); - } - */ + if (entry.getValue() instanceof Map) { + Map nestedMap = (Map) entry.getValue(); + for (Map.Entry nestedEntry : nestedMap.entrySet()) { + outputString.append(String.format("%-40s : %s%n", nestedEntry.getKey(), nestedEntry.getValue())); + } } else { outputString.append(String.format("%-40s : %s%n", entry.getKey(), entry.getValue())); } From 3855bde4728c3042d23c1c5f0c249b72ab2d3760 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 7 Oct 2025 11:03:26 +0200 Subject: [PATCH 071/257] feat: concluded connecting to other groups functionality --- .../feature/model/cli/PrintStatistics.java | 32 +++++++------------ 1 file changed, 12 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 5ba02860..7c585a3e 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -113,7 +113,6 @@ private void writeTo(Path path, String type) { case "xml": // TODO future Story Card: Write to XML // IO.save(new Object(data), path, new XMLFeatureModelFormat()); - // IO.sa break; case "csv": // TODO future Story Card: Write to CSV @@ -142,6 +141,9 @@ private LinkedHashMap collectStats(AnalysesScope scope) { data = new LinkedHashMap(); if (scope == AnalysesScope.ALL || scope == AnalysesScope.CONSTRAINT_RELATED) { + + data.put("CONSTRAINT RELATED STATS", null); + // Fetching constraint related statistics data.put( "Number of Atoms", @@ -156,34 +158,20 @@ private LinkedHashMap collectStats(AnalysesScope scope) { HashMap computational_opDensity = Computations.of(model).map(ComputeOperatorDistribution::new).compute(); - /* - for(int i = 0; i < computational_opDensity.size(); i++) { - - }*/ - data.put("Operator Distribution", computational_opDensity); - - /* - data.put("Operator Density AND", computational_opDensity.get("And")); - data.put("Operator Density Or", computational_opDensity.get("Or")); - data.put("Operator Density Not", computational_opDensity.get("Not")); - data.put("Operator Density Implies", computational_opDensity.get("Implies")); - */ } if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { - // Fetching tree related statistics + data.put("TREE RELATED STATS", null); - model.mutate().addFeatureTreeRoot(model.mutate().addFeature("Second Tree Root")); + // Fetching tree related statistics List trees = model.getRoots(); String treePrefix; for (int i = 0; i < trees.size(); i++) { treePrefix = "[Tree " + (i + 1) + "] "; - // treePrefix = "\t"; - // data.put("[Tree "+(i+1) + "]", ""); IFeatureTree tree = trees.get(i); @@ -232,15 +220,19 @@ public void printStatsPretty() { private StringBuilder buildPrettyStats() { StringBuilder outputString = new StringBuilder(); - // if(isInstance(Map.Entry, LinkedHashMap) ) - for (Map.Entry entry : data.entrySet()) { if (entry.getValue() instanceof Map) { Map nestedMap = (Map) entry.getValue(); + + outputString.append(String.format("%-40s%n", entry.getKey())); + for (Map.Entry nestedEntry : nestedMap.entrySet()) { - outputString.append(String.format("%-40s : %s%n", nestedEntry.getKey(), nestedEntry.getValue())); + outputString.append( + String.format("%-33s : %s%n", "\t " + nestedEntry.getKey(), nestedEntry.getValue())); } + } else if (entry.getValue() == null) { + outputString.append(String.format("\n\t\t%-40s %n", entry.getKey() + "\n")); } else { outputString.append(String.format("%-40s : %s%n", entry.getKey(), entry.getValue())); } From a4db9031cb123e7346431606ea5a73c362092198 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 7 Oct 2025 13:49:45 +0200 Subject: [PATCH 072/257] test: continuing work on tests --- .../feature/model/cli/PrintStatistics.java | 48 +++++++------- .../model/cli/PrintStatisticsTest.java | 63 +++++++++++++++---- 2 files changed, 77 insertions(+), 34 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 7c585a3e..01a58967 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -65,10 +65,6 @@ enum AnalysesScope { public static final Option PRETTY_PRINT = Option.newFlag("pretty").setDescription("Pretty prints the numbers"); - private LinkedHashMap data; - private FeatureModel model; - private IFeatureModel imodel; - @Override public int run(OptionList optionParser) { @@ -80,14 +76,15 @@ public int run(OptionList optionParser) { // opening input model Path path = optionParser.getResult(INPUT_OPTION).orElseThrow(); Result load = IO.load(path, FeatureModelFormats.getInstance()); - imodel = load.orElseThrow(); - model = (FeatureModel) imodel; + LinkedHashMap data; + + FeatureModel model = (FeatureModel) load.orElseThrow(); // collecting statistics of the model, checking if scope is specified if (optionParser.getResult(ANALYSES_SCOPE).isPresent()) { - data = collectStats(optionParser.get(ANALYSES_SCOPE)); + data = collectStats(model, optionParser.get(ANALYSES_SCOPE)); } else { - data = collectStats(AnalysesScope.ALL); + data = collectStats(model, AnalysesScope.ALL); } // if output path is specified, write statistics to file @@ -99,9 +96,9 @@ public int run(OptionList optionParser) { // printing statistics to console if (optionParser.get(PRETTY_PRINT)) { - printStatsPretty(); + printStatsPretty(data); } else { - printStats(); + printStats(data); } return exit_status; @@ -136,14 +133,12 @@ private void writeTo(Path path, String type) { } } - private LinkedHashMap collectStats(AnalysesScope scope) { + public LinkedHashMap collectStats(FeatureModel model, AnalysesScope scope) { - data = new LinkedHashMap(); + LinkedHashMap data = new LinkedHashMap(); if (scope == AnalysesScope.ALL || scope == AnalysesScope.CONSTRAINT_RELATED) { - data.put("CONSTRAINT RELATED STATS", null); - // Fetching constraint related statistics data.put( "Number of Atoms", @@ -163,7 +158,6 @@ private LinkedHashMap collectStats(AnalysesScope scope) { if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { - data.put("TREE RELATED STATS", null); // Fetching tree related statistics @@ -213,15 +207,24 @@ private LinkedHashMap collectStats(AnalysesScope scope) { return data; } - public void printStatsPretty() { - FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + buildPrettyStats()); + public void printStatsPretty(LinkedHashMap data) { + FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + buildStringPrettyStats(data)); } - private StringBuilder buildPrettyStats() { + public StringBuilder buildStringPrettyStats(LinkedHashMap data) { StringBuilder outputString = new StringBuilder(); - + + + for (Map.Entry entry : data.entrySet()) { - + + if(entry.getKey().equals("Number of Atoms")) { + outputString.append(String.format("\n\t\t%-40s %n", "CONSTRAINT RELATED STATS\n")); + + } else if(entry.getKey().equals("[Tree 1] Average Number of Childen")) { + outputString.append(String.format("\n\t\t%-40s %n", "TREE RELATED STATS\n")); + + } if (entry.getValue() instanceof Map) { Map nestedMap = (Map) entry.getValue(); @@ -231,8 +234,6 @@ private StringBuilder buildPrettyStats() { outputString.append( String.format("%-33s : %s%n", "\t " + nestedEntry.getKey(), nestedEntry.getValue())); } - } else if (entry.getValue() == null) { - outputString.append(String.format("\n\t\t%-40s %n", entry.getKey() + "\n")); } else { outputString.append(String.format("%-40s : %s%n", entry.getKey(), entry.getValue())); } @@ -240,7 +241,7 @@ private StringBuilder buildPrettyStats() { return outputString; } - public void printStats() { + public void printStats(LinkedHashMap data) { FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + data); } @@ -253,4 +254,5 @@ public Optional getDescription() { public Optional getShortName() { return Optional.of("printStats"); } + } diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 0af4d877..95571602 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -24,7 +24,16 @@ import de.featjar.base.FeatJAR; import de.featjar.base.data.identifier.AIdentifier; +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; + +import java.io.ByteArrayOutputStream; import java.io.IOException; +import java.util.LinkedHashMap; +import java.util.Map; + import org.junit.jupiter.api.Test; /** @@ -35,7 +44,14 @@ public class PrintStatisticsTest { PrintStatistics printStats = new PrintStatistics(); + FeatureModel minimalModel = generateMinimalModel(); + private FeatureModel generateMinimalModel() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + return featureModel; + } + @Test void inputTest() throws IOException { @@ -86,22 +102,47 @@ void outputWithoutFileExtension() throws IOException { "desktop/folder"); assertEquals(1, exit_code); } - - @Test - void printPretty() throws IOException {} - + @Test - void printDefault() throws IOException {} + void scopeAll() throws IOException { + String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}, [Tree 1] Average Number of Childen=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + String comparison = printStats.collectStats(minimalModel, AnalysesScope.ALL).toString(); + assertEquals(content, comparison); + + } @Test - void scopeAll() throws IOException {} + void scopeTreeRelated() throws IOException { + String content = "{[Tree 1] Average Number of Childen=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + String comparison = printStats.collectStats(minimalModel, AnalysesScope.TREE_RELATED).toString(); + assertEquals(content, comparison); + } @Test - void scopeTreeRelated() throws IOException {} - + void scopeConstraintRelated() throws IOException { + String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}}"; + String comparison = printStats.collectStats(minimalModel, AnalysesScope.CONSTRAINT_RELATED).toString(); + assertEquals(content, comparison); + } + @Test - void scopeConstraintRelated() throws IOException {} + void prettyStringBuilder() throws IOException { + + LinkedHashMap testData = new LinkedHashMap<>(); + testData.put("Normal Entry", 10); + // LinkedHashMap nestedMap = new LinkedHashMap<>(); + // nestedMap.put("Nested Entry 1", 5); + // nestedMap.put("Nested Entry 2", 6); + // testData.put("HashMap Entry", nestedMap); + // testData.put("Number of Atoms", ""); + // testData.put("[Tree 1] Average Number of Childen", ""); + + StringBuilder comparison = new StringBuilder(); + comparison.append("Normal Entry : 10\n"); + + System.out.println(printStats.buildStringPrettyStats(testData)); + + assertEquals(printStats.buildStringPrettyStats(testData), comparison); - @Test - void scopeNotSpecified() throws IOException {} + } } From a7db2fa44cf5ae661e0e8e20f592f438912e9825 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 7 Oct 2025 13:50:57 +0200 Subject: [PATCH 073/257] test: added string difference finder method --- .../feature/model/cli/PrintStatisticsTest.java | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 95571602..4c0a4a7a 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -51,7 +51,22 @@ private FeatureModel generateMinimalModel() { featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); return featureModel; } - + + public static int indexOfDifference(String s1, String s2) { + int minLen = Math.min(s1.length(), s2.length()); + for (int i = 0; i < minLen; i++) { + if (s1.charAt(i) != s2.charAt(i)) { + return i; // index where difference starts + } + } + if (s1.length() == s2.length()) { + return -1; // strings are equal + } else { + return minLen; // difference due to length + } + } + + @Test void inputTest() throws IOException { From 7c740823d82351dd2adb0ecc9bb6aa7ffa811ce7 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 7 Oct 2025 14:18:20 +0200 Subject: [PATCH 074/257] test: tests concluded --- .../feature/model/cli/PrintStatistics.java | 37 ++++---- .../model/cli/PrintStatisticsTest.java | 90 +++++++++---------- 2 files changed, 58 insertions(+), 69 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 01a58967..7d6a3bd1 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -55,7 +55,7 @@ enum AnalysesScope { TREE_RELATED, CONSTRAINT_RELATED } - + private int exit_status = 0; // options as command line arguments @@ -84,7 +84,7 @@ public int run(OptionList optionParser) { if (optionParser.getResult(ANALYSES_SCOPE).isPresent()) { data = collectStats(model, optionParser.get(ANALYSES_SCOPE)); } else { - data = collectStats(model, AnalysesScope.ALL); + data = collectStats(model, AnalysesScope.ALL); } // if output path is specified, write statistics to file @@ -94,12 +94,12 @@ public int run(OptionList optionParser) { writeTo(optionParser.getResult(OUTPUT_OPTION).get(), fileExtension); } - // printing statistics to console + // printing statistics to console if (optionParser.get(PRETTY_PRINT)) { - printStatsPretty(data); - } else { - printStats(data); - } + printStatsPretty(data); + } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { + printStats(data); + } return exit_status; } @@ -135,7 +135,7 @@ private void writeTo(Path path, String type) { public LinkedHashMap collectStats(FeatureModel model, AnalysesScope scope) { - LinkedHashMap data = new LinkedHashMap(); + LinkedHashMap data = new LinkedHashMap(); if (scope == AnalysesScope.ALL || scope == AnalysesScope.CONSTRAINT_RELATED) { @@ -158,7 +158,6 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { - // Fetching tree related statistics List trees = model.getRoots(); @@ -213,18 +212,15 @@ public void printStatsPretty(LinkedHashMap data) { public StringBuilder buildStringPrettyStats(LinkedHashMap data) { StringBuilder outputString = new StringBuilder(); - - - + for (Map.Entry entry : data.entrySet()) { - - if(entry.getKey().equals("Number of Atoms")) { - outputString.append(String.format("\n\t\t%-40s %n", "CONSTRAINT RELATED STATS\n")); - - } else if(entry.getKey().equals("[Tree 1] Average Number of Childen")) { - outputString.append(String.format("\n\t\t%-40s %n", "TREE RELATED STATS\n")); - - } + + if (entry.getKey().equals("Number of Atoms")) { + outputString.append(String.format("\n\t\t%-40s %n", "CONSTRAINT RELATED STATS\n")); + + } else if (entry.getKey().equals("[Tree 1] Average Number of Childen")) { + outputString.append(String.format("\n\t\t%-40s %n", "TREE RELATED STATS\n")); + } if (entry.getValue() instanceof Map) { Map nestedMap = (Map) entry.getValue(); @@ -254,5 +250,4 @@ public Optional getDescription() { public Optional getShortName() { return Optional.of("printStats"); } - } diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 4c0a4a7a..af891866 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -26,14 +26,9 @@ import de.featjar.base.data.identifier.AIdentifier; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; - -import java.io.ByteArrayOutputStream; import java.io.IOException; import java.util.LinkedHashMap; -import java.util.Map; - import org.junit.jupiter.api.Test; /** @@ -52,21 +47,6 @@ private FeatureModel generateMinimalModel() { return featureModel; } - public static int indexOfDifference(String s1, String s2) { - int minLen = Math.min(s1.length(), s2.length()); - for (int i = 0; i < minLen; i++) { - if (s1.charAt(i) != s2.charAt(i)) { - return i; // index where difference starts - } - } - if (s1.length() == s2.length()) { - return -1; // strings are equal - } else { - return minLen; // difference due to length - } - } - - @Test void inputTest() throws IOException { @@ -117,47 +97,61 @@ void outputWithoutFileExtension() throws IOException { "desktop/folder"); assertEquals(1, exit_code); } - + @Test void scopeAll() throws IOException { - String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}, [Tree 1] Average Number of Childen=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; - String comparison = printStats.collectStats(minimalModel, AnalysesScope.ALL).toString(); - assertEquals(content, comparison); - + String content = + "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}, [Tree 1] Average Number of Childen=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + String comparison = + printStats.collectStats(minimalModel, AnalysesScope.ALL).toString(); + assertEquals(content, comparison); } @Test void scopeTreeRelated() throws IOException { - String content = "{[Tree 1] Average Number of Childen=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; - String comparison = printStats.collectStats(minimalModel, AnalysesScope.TREE_RELATED).toString(); - assertEquals(content, comparison); + String content = + "{[Tree 1] Average Number of Childen=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + String comparison = printStats + .collectStats(minimalModel, AnalysesScope.TREE_RELATED) + .toString(); + assertEquals(content, comparison); } @Test void scopeConstraintRelated() throws IOException { - String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}}"; - String comparison = printStats.collectStats(minimalModel, AnalysesScope.CONSTRAINT_RELATED).toString(); - assertEquals(content, comparison); + String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}}"; + String comparison = printStats + .collectStats(minimalModel, AnalysesScope.CONSTRAINT_RELATED) + .toString(); + assertEquals(content, comparison); } - + @Test void prettyStringBuilder() throws IOException { - - LinkedHashMap testData = new LinkedHashMap<>(); - testData.put("Normal Entry", 10); - // LinkedHashMap nestedMap = new LinkedHashMap<>(); - // nestedMap.put("Nested Entry 1", 5); - // nestedMap.put("Nested Entry 2", 6); - // testData.put("HashMap Entry", nestedMap); - // testData.put("Number of Atoms", ""); - // testData.put("[Tree 1] Average Number of Childen", ""); - - StringBuilder comparison = new StringBuilder(); - comparison.append("Normal Entry : 10\n"); - - System.out.println(printStats.buildStringPrettyStats(testData)); - - assertEquals(printStats.buildStringPrettyStats(testData), comparison); + LinkedHashMap testData = new LinkedHashMap<>(); + testData.put("Normal Entry", 10); + LinkedHashMap nestedMap = new LinkedHashMap<>(); + nestedMap.put("Nested Entry 1", 5); + nestedMap.put("Nested Entry 2", 6); + testData.put("HashMap Entry", nestedMap); + testData.put("Number of Atoms", ""); + testData.put("[Tree 1] Average Number of Childen", ""); + + StringBuilder comparison = new StringBuilder(); + comparison.append("Normal Entry : 10\n" + + "HashMap Entry \n" + + " Nested Entry 1 : 5\n" + + " Nested Entry 2 : 6\n" + + "\n" + + " CONSTRAINT RELATED STATS\n" + + " \n" + + "Number of Atoms : \n" + + "\n" + + " TREE RELATED STATS\n" + + " \n" + + "[Tree 1] Average Number of Childen : \n"); + + assertEquals(printStats.buildStringPrettyStats(testData).toString(), comparison.toString()); } } From 5ab6fcb46a72647650827671f8d17bf5ceb5779f Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 7 Oct 2025 14:18:40 +0200 Subject: [PATCH 075/257] test: tests concluded --- .../de/featjar/feature/model/cli/PrintStatistics.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 7d6a3bd1..aff6b5d0 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -55,7 +55,7 @@ enum AnalysesScope { TREE_RELATED, CONSTRAINT_RELATED } - + private int exit_status = 0; // options as command line arguments @@ -94,12 +94,12 @@ public int run(OptionList optionParser) { writeTo(optionParser.getResult(OUTPUT_OPTION).get(), fileExtension); } - // printing statistics to console + // printing statistics to console if (optionParser.get(PRETTY_PRINT)) { - printStatsPretty(data); + printStatsPretty(data); } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { - printStats(data); - } + printStats(data); + } return exit_status; } From 598c324d71a3050d35e68b203d3dd5d34a7c4993 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 7 Oct 2025 14:21:26 +0200 Subject: [PATCH 076/257] docs: added test stubs + fixed Childen / Children typo --- .../feature/model/cli/PrintStatistics.java | 42 ++++++++++++++++++- .../model/cli/PrintStatisticsTest.java | 2 +- 2 files changed, 41 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index aff6b5d0..8d0f23e4 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -65,6 +65,12 @@ enum AnalysesScope { public static final Option PRETTY_PRINT = Option.newFlag("pretty").setDescription("Pretty prints the numbers"); + /** + * + * @param optionParser the option parser + * + * {@return If the return description covers your doc, put the @return line in curly brackets} + */ @Override public int run(OptionList optionParser) { @@ -104,6 +110,11 @@ public int run(OptionList optionParser) { return exit_status; } + /** + * + * @param path + * @param type + */ private void writeTo(Path path, String type) { switch (type) { @@ -133,6 +144,12 @@ private void writeTo(Path path, String type) { } } + /** + * + * @param model + * @param scope + * @return + */ public LinkedHashMap collectStats(FeatureModel model, AnalysesScope scope) { LinkedHashMap data = new LinkedHashMap(); @@ -170,7 +187,7 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc // avg num of children data.put( - treePrefix + "Average Number of Childen", + treePrefix + "Average Number of Children", Computations.of(tree) .map(ComputeFeatureAverageNumberOfChildren::new) .compute()); @@ -206,10 +223,19 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc return data; } + /** + * + * @param data + */ public void printStatsPretty(LinkedHashMap data) { FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + buildStringPrettyStats(data)); } + /** + * + * @param data + * @return + */ public StringBuilder buildStringPrettyStats(LinkedHashMap data) { StringBuilder outputString = new StringBuilder(); @@ -218,7 +244,7 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) if (entry.getKey().equals("Number of Atoms")) { outputString.append(String.format("\n\t\t%-40s %n", "CONSTRAINT RELATED STATS\n")); - } else if (entry.getKey().equals("[Tree 1] Average Number of Childen")) { + } else if (entry.getKey().equals("[Tree 1] Average Number of Children")) { outputString.append(String.format("\n\t\t%-40s %n", "TREE RELATED STATS\n")); } if (entry.getValue() instanceof Map) { @@ -237,15 +263,27 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) return outputString; } + /** + * + * @param data + */ public void printStats(LinkedHashMap data) { FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + data); } + /** + * + * @return + */ @Override public Optional getDescription() { return Optional.of("Prints out statistics about a given Feature Model."); } + /** + * + * @return + */ @Override public Optional getShortName() { return Optional.of("printStats"); diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index af891866..76fb49aa 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -150,7 +150,7 @@ void prettyStringBuilder() throws IOException { + "\n" + " TREE RELATED STATS\n" + " \n" - + "[Tree 1] Average Number of Childen : \n"); + + "[Tree 1] Average Number of Children : \n"); assertEquals(printStats.buildStringPrettyStats(testData).toString(), comparison.toString()); } From 64bc0c81c01ac1731e6da2c2e2494b1b0fbc5b26 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 7 Oct 2025 14:33:45 +0200 Subject: [PATCH 077/257] docs: added docs for the last few methods --- .../de/featjar/feature/model/cli/PrintStatistics.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 8d0f23e4..2d5eba2b 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -264,8 +264,8 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) } /** - * - * @param data + * Prints gathered statistics in a compact format. + * @param data: the previously computed data packaged line by line: String names the stat, Object holds the data. */ public void printStats(LinkedHashMap data) { FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + data); @@ -273,7 +273,7 @@ public void printStats(LinkedHashMap data) { /** * - * @return + * {@return brief description of this class} */ @Override public Optional getDescription() { @@ -282,7 +282,7 @@ public Optional getDescription() { /** * - * @return + * {@return short name of this class} */ @Override public Optional getShortName() { From 1bdede4d1fab6a5f38d4fb0b6dac55b96644a107 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 7 Oct 2025 14:34:49 +0200 Subject: [PATCH 078/257] docs: added documentation --- .../feature/model/cli/PrintStatistics.java | 27 ++++++++----------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 2d5eba2b..f548dbe3 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -46,7 +46,7 @@ /** * Prints statistics about a provided Feature Model. * - * @author Knut & Kilian + * @author Knut, Kilian & Benjamin */ public class PrintStatistics extends ACommand { @@ -58,7 +58,7 @@ enum AnalysesScope { private int exit_status = 0; - // options as command line arguments + // command line options for user customization public static final Option ANALYSES_SCOPE = Option.newEnumOption("scope", AnalysesScope.class).setDescription("Specifies scope of statistics"); @@ -66,10 +66,10 @@ enum AnalysesScope { Option.newFlag("pretty").setDescription("Pretty prints the numbers"); /** - * + * main method for gathering, printing and writing statistics of a feature model * @param optionParser the option parser * - * {@return If the return description covers your doc, put the @return line in curly brackets} + * @return returns 0 if successful, 1 in case of error */ @Override public int run(OptionList optionParser) { @@ -100,7 +100,7 @@ public int run(OptionList optionParser) { writeTo(optionParser.getResult(OUTPUT_OPTION).get(), fileExtension); } - // printing statistics to console + // printing statistics to console if no output file is specified if (optionParser.get(PRETTY_PRINT)) { printStatsPretty(data); } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { @@ -111,9 +111,9 @@ public int run(OptionList optionParser) { } /** - * + * writes statistics into a file, depending on file type * @param path - * @param type + * @param type: is extracted from provided output path, needs to be lower case */ private void writeTo(Path path, String type) { @@ -145,10 +145,10 @@ private void writeTo(Path path, String type) { } /** - * + * method for collecting statistics of the provided feature model depending on specified scope of information (all, constraint related, tree related) * @param model * @param scope - * @return + * @return LinkedHashMap with stats data, keys are descriptive strings, values types depend on statistic (Integer, Float, HashMap) */ public LinkedHashMap collectStats(FeatureModel model, AnalysesScope scope) { @@ -156,7 +156,7 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc if (scope == AnalysesScope.ALL || scope == AnalysesScope.CONSTRAINT_RELATED) { - // Fetching constraint related statistics + // fetching constraint related statistics data.put( "Number of Atoms", Computations.of(model).map(ComputeAtomsCount::new).compute()); @@ -175,7 +175,7 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { - // Fetching tree related statistics + // fetching tree related statistics List trees = model.getRoots(); String treePrefix; @@ -185,33 +185,28 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc IFeatureTree tree = trees.get(i); - // avg num of children data.put( treePrefix + "Average Number of Children", Computations.of(tree) .map(ComputeFeatureAverageNumberOfChildren::new) .compute()); - // num of top features data.put( treePrefix + "Number of Top Features", Computations.of(tree) .map(ComputeFeatureTopFeatures::new) .compute()); - // num of leaf features data.put( treePrefix + "Number of Leaf Features", Computations.of(tree) .map(ComputeFeatureFeaturesCounter::new) .compute()); - // tree depth data.put( treePrefix + "Tree Depth", Computations.of(tree).map(ComputeFeatureTreeDepth::new).compute()); - // group distribution data.put( treePrefix + "Group Distribution", Computations.of(tree) From b98922ee541b4963b94b9f4ece9b2712d78892e7 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 7 Oct 2025 14:59:10 +0200 Subject: [PATCH 079/257] test: improved test of print function --- .../model/cli/PrintStatisticsTest.java | 22 +++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 76fb49aa..e566e70b 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -38,6 +38,20 @@ */ public class PrintStatisticsTest { + public static int indexOfDifference(String s1, String s2) { + int minLen = Math.min(s1.length(), s2.length()); + for (int i = 0; i < minLen; i++) { + if (s1.charAt(i) != s2.charAt(i)) { + // System.out.println(s1.charAt(i-1) +"(" + s1.charAt(i) + ")" + s1.charAt(i+1)); + System.out.println(s1.substring(340)); + System.out.println("---------------------"); + System.out.println(s2.substring(340)); + return i; + } + } + return -1; + } + PrintStatistics printStats = new PrintStatistics(); FeatureModel minimalModel = generateMinimalModel(); @@ -101,7 +115,7 @@ void outputWithoutFileExtension() throws IOException { @Test void scopeAll() throws IOException { String content = - "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}, [Tree 1] Average Number of Childen=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}, [Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; String comparison = printStats.collectStats(minimalModel, AnalysesScope.ALL).toString(); assertEquals(content, comparison); @@ -110,7 +124,7 @@ void scopeAll() throws IOException { @Test void scopeTreeRelated() throws IOException { String content = - "{[Tree 1] Average Number of Childen=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + "{[Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; String comparison = printStats .collectStats(minimalModel, AnalysesScope.TREE_RELATED) .toString(); @@ -136,7 +150,7 @@ void prettyStringBuilder() throws IOException { nestedMap.put("Nested Entry 2", 6); testData.put("HashMap Entry", nestedMap); testData.put("Number of Atoms", ""); - testData.put("[Tree 1] Average Number of Childen", ""); + testData.put("[Tree 1] Average Number of Children", ""); StringBuilder comparison = new StringBuilder(); comparison.append("Normal Entry : 10\n" @@ -150,7 +164,7 @@ void prettyStringBuilder() throws IOException { + "\n" + " TREE RELATED STATS\n" + " \n" - + "[Tree 1] Average Number of Children : \n"); + + "[Tree 1] Average Number of Children : \n"); assertEquals(printStats.buildStringPrettyStats(testData).toString(), comparison.toString()); } From cb9590f51e76231f983975eb86602128930289f5 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 7 Oct 2025 15:01:40 +0200 Subject: [PATCH 080/257] refactor: removed test method --- .../feature/model/cli/PrintStatisticsTest.java | 15 +-------------- 1 file changed, 1 insertion(+), 14 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index e566e70b..c19e18a6 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -24,6 +24,7 @@ import de.featjar.base.FeatJAR; import de.featjar.base.data.identifier.AIdentifier; +import de.featjar.base.data.identifier.IIdentifiable; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; @@ -38,20 +39,6 @@ */ public class PrintStatisticsTest { - public static int indexOfDifference(String s1, String s2) { - int minLen = Math.min(s1.length(), s2.length()); - for (int i = 0; i < minLen; i++) { - if (s1.charAt(i) != s2.charAt(i)) { - // System.out.println(s1.charAt(i-1) +"(" + s1.charAt(i) + ")" + s1.charAt(i+1)); - System.out.println(s1.substring(340)); - System.out.println("---------------------"); - System.out.println(s2.substring(340)); - return i; - } - } - return -1; - } - PrintStatistics printStats = new PrintStatistics(); FeatureModel minimalModel = generateMinimalModel(); From a59aba9af9461c2173fb2d7e106cd835a8d67008 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 7 Oct 2025 16:56:46 +0200 Subject: [PATCH 081/257] feat: general structure of functionality --- .../feature/model/cli/FormatConversion.java | 134 ++++++++++++++++++ src/main/resources/extensions.xml | 1 + 2 files changed, 135 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/cli/FormatConversion.java diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java new file mode 100644 index 00000000..0a79515e --- /dev/null +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.cli; + +import de.featjar.base.FeatJAR; +import de.featjar.base.cli.ACommand; +import de.featjar.base.cli.ICommand; +import de.featjar.base.cli.Option; +import de.featjar.base.cli.OptionList; +import de.featjar.base.computation.Computations; +import de.featjar.base.data.Result; +import de.featjar.base.io.IO; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeatureModel; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.*; +import de.featjar.feature.model.computation.ComputeAtomsCount; +import de.featjar.feature.model.computation.ComputeAverageConstraint; +import de.featjar.feature.model.computation.ComputeFeatureDensity; +import de.featjar.feature.model.computation.ComputeOperatorDistribution; +import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.feature.model.io.xml.GraphVizFeatureModelFormat; +import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * Prints statistics about a provided Feature Model. + * + * @author Knut, Kilian & Benjamin + */ +public class FormatConversion implements ICommand { + + public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) + .setDescription("Path to input file. Accepted File Types: csv, xml, yaml, txt") + .setValidator(Option.PathValidator); + + /** + * Output option for saving files. + */ + public static final Option OUTPUT_OPTION = + Option.newOption("output", Option.PathParser).setDescription("Path to output file. Accepted File Types: csv, xml, yaml, txt, json"); + + /** + * {@return all options registered for the calling class} + */ + public final List> getOptions() { + return Option.getAllOptions(getClass()); + } + + //--input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output "c://home/deskop/model.xml" + + /** + * + * @param optionParser the option parser + * + * @return returns 0 if successful, 1 in case of error + */ + @Override + public int run(OptionList optionParser) { + + // Valid formats: XML, UVL, GraphVis + + // opening file + Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); + Result load = IO.load(inputPath, FeatureModelFormats.getInstance()); + FeatureModel model = (FeatureModel) load.orElseThrow(); + + // saving file + Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); + + Path test = Paths.get(""); + System.out.println(test.toAbsolutePath()); + + Path target = Paths.get(test.toAbsolutePath()+"model.dot"); + + + try { + + IO.save(model, target, new XMLFeatureModelFormat()); + FeatJAR.log().message("HALLO"); + + }catch (Exception e) { + FeatJAR.log().error(e.getMessage()); + } + + + return 0; + } + + + + /** + * + * {@return brief description of this class} + */ + @Override + public Optional getDescription() { + return Optional.of("Convert exisiting file of feature model into new format."); + } + + /** + * + * {@return short name of this class} + */ + @Override + public Optional getShortName() { + return Optional.of("formatConversion"); + } +} diff --git a/src/main/resources/extensions.xml b/src/main/resources/extensions.xml index ae7ffdfe..f5cef2cd 100644 --- a/src/main/resources/extensions.xml +++ b/src/main/resources/extensions.xml @@ -6,5 +6,6 @@ + From 571583da22a56cb1df5a4be3de2be2a2aee41b1f Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Tue, 7 Oct 2025 17:12:52 +0200 Subject: [PATCH 082/257] feat: added temprery class JSON and AnalysisTree. --- .../feature/model/analysis/AnalysisTree.java | 97 +++++++++++++++++++ .../model/io/json/JSONFeatureModelFormat.java | 54 +++++++++++ .../ConstraintPropertiesVisitorTest.java | 14 +-- .../feature/model/io/JSONFExportTest.java | 86 ++++++++++++++++ 4 files changed, 244 insertions(+), 7 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java create mode 100644 src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java create mode 100644 src/test/java/de/featjar/feature/model/io/JSONFExportTest.java diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java new file mode 100644 index 00000000..98ccd8b4 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.tree.structure.ATree; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Objects; + +public class AnalysisTree extends ATree> { + + String name; + T value; + + public AnalysisTree(String name, T value) { + this.name = name; + this.value = value; + } + + // public AnalysisTree(Hash) { + // this.name = name; + // } + + public String getName() { + return this.name; + } + + public T getValue() { + return this.value; + } + + public AnalysisTree(AnalysisTree analysisTree) { + this.name = analysisTree.name; + } + + @Override + public AnalysisTree cloneNode() { + // TODO Auto-generated method stub + return new AnalysisTree(this); + } + + @Override + public boolean equalsNode(AnalysisTree other) { + return this.name.equals(other.name); + } + + // TODO is this hashing OK? + @Override + public int hashCodeNode() { + return Objects.hash(this.getClass(), this.name); + } + + public static AnalysisTree hashMapToTree(HashMap hashMap, String name) { + AnalysisTree root = new AnalysisTree<>(name, (Object) null); + for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { + String currentKey = iterator.next(); + /*if (obj.getClass().equals("HashMap")) { + hashMapToTree((HashMap)obj); + }*/ + if (hashMap.get(currentKey) instanceof Integer) { + root.addChild(new AnalysisTree<>(currentKey, (int) hashMap.get(currentKey))); + } else if (hashMap.get(currentKey) instanceof Float) { + root.addChild(new AnalysisTree<>(currentKey, (float) hashMap.get(currentKey))); + } else if (hashMap.get(currentKey) instanceof Double) { + root.addChild(new AnalysisTree<>(currentKey, (double) hashMap.get(currentKey))); + } else if (hashMap.get(currentKey) instanceof HashMap) { + root.addChild(hashMapToTree((HashMap) hashMap.get(currentKey), currentKey)); + } else { + System.out.println("ERROR HELP ME"); + } + } + return root; + } + + @Override + public String toString() { + return "" + this.getClass() + " " + this.name; + } +} diff --git a/src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java b/src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java new file mode 100644 index 00000000..93d01067 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.io.json; + +import de.featjar.base.data.Result; +import de.featjar.base.io.format.IFormat; +import java.util.HashMap; +import org.json.JSONObject; + +public class JSONFeatureModelFormat implements IFormat> { + + @Override + public String getName() { + return "JSON"; + } + + @Override + public String getFileExtension() { + return "json"; + } + + @Override + public boolean supportsParse() { + return false; + } + + @Override + public boolean supportsWrite() { + return true; + } + + @Override + public Result serialize(HashMap object) { + return Result.of(new JSONObject(object).toString(1)); // new XMLFeatureModelWriter().serialize(object); + } +} diff --git a/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java index 274adbc7..03d7ca4c 100644 --- a/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java @@ -28,10 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import de.featjar.base.tree.Trees; -import de.featjar.base.tree.structure.ITree; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.formula.structure.IExpression; import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.And; import de.featjar.formula.structure.connective.Implies; @@ -44,8 +42,6 @@ import de.featjar.formula.structure.term.value.Constant; import java.util.HashMap; import java.util.Iterator; -import java.util.List; -import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.Test; @@ -68,7 +64,8 @@ public void atomsVisitorTest() { assertEquals(1, formula1BooleansCount); assertEquals(3, formula2ConstantsCount); assertEquals(1, formula3VariablesCount); - assertEquals(0, Trees.traverse(new And(), new AtomsCount(true, true, true)).orElseThrow()); + assertEquals( + 0, Trees.traverse(new And(), new AtomsCount(true, true, true)).orElseThrow()); } @Test @@ -102,7 +99,8 @@ public void featureDensityVisitorTest() { assertEquals(1, formula3Features.size()); assertTrue(formula3Features.contains("a")); assertFalse(formula3Features.contains("b")); - assertEquals(0, Trees.traverse(new And(), new FeatureDensity()).orElseThrow().size()); + assertEquals( + 0, Trees.traverse(new And(), new FeatureDensity()).orElseThrow().size()); } @Test @@ -133,7 +131,9 @@ public void operatorDensityVisitorTest() { assertEquals(4, formula2Count.size()); assertEquals(0, formula3Count.size()); - assertEquals(0, Trees.traverse(null, new OperatorDistribution()).orElseThrow().size()); + assertEquals( + 0, + Trees.traverse(null, new OperatorDistribution()).orElseThrow().size()); } public FeatureModel createFeatureModel() { diff --git a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java new file mode 100644 index 00000000..a39ec759 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java @@ -0,0 +1,86 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.io; + +import de.featjar.base.io.IO; +import de.featjar.base.tree.Trees; +import de.featjar.base.tree.visitor.TreePrinter; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.io.json.JSONFeatureModelFormat; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Paths; +import java.util.Iterator; +import java.util.LinkedHashMap; +import org.json.JSONObject; +import org.junit.jupiter.api.Test; + +public class JSONFExportTest { + + LinkedHashMap data = new LinkedHashMap(); + + @Test + public void JSONTest() throws IOException { + LinkedHashMap innerMap = new LinkedHashMap(); + innerMap.put("xo", 3.3); + innerMap.put("numOfLeafFeatures", (float) 12.4); + data.put("numOfTopFeatures", 3.3); + data.put("numOfLeafFeatures", (float) 12.4); + data.put("treeDepth", 3); + data.put("avgNumOfChildren", 3); + data.put("numInOrGroups", 7); + data.put("numInAltGroups", 5); + data.put("avgNumOfAtomsPerConstraints", innerMap); + data.put("numOfAtoms", 8); + data.put("avgNumOfAsss", 4); + + FileSystem fileSystem = FileSystems.getDefault(); + JSONFeatureModelFormat jsonFormat = new JSONFeatureModelFormat(); + System.out.println(jsonFormat.serialize(data).get()); + try { + FileOutputStream outputStream = new FileOutputStream("filename.json"); + IO.save(data, Paths.get("filename.json"), new JSONFeatureModelFormat()); + System.out.println("no error"); + } catch (Exception e) { + System.out.println(e); + } + + JSONObject jsonobj = new JSONObject(data); + System.out.println(jsonobj.toString(1)); + JSONObject jsonobj1 = new JSONObject(jsonobj.toString(1)); + // for (Iterator iterator = jsonobj1.keys(); iterator.hasNext();) { + // System.out.println(jsonobj1.get(iterator.next().toString()).getClass()); + + // } + + for (Iterator iterator = data.keySet().iterator(); iterator.hasNext(); ) { + String currentKey = iterator.next(); + System.out.println((data.get(currentKey)).getClass().isPrimitive()); + // System.out.println((data.get(iterator.next())).getClass()); + } + + AnalysisTree analsyisTree = AnalysisTree.hashMapToTree(data, "Analysis"); + + System.out.println(Trees.traverse(analsyisTree, new TreePrinter()).get()); + } +} From af9b6511c3ac639e573e216554125cba455ced84 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 8 Oct 2025 09:31:21 +0200 Subject: [PATCH 083/257] feat: just completed some functionality in the class AnalysisTree --- .../feature/model/analysis/AnalysisTree.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index 98ccd8b4..d352854e 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -35,10 +35,6 @@ public AnalysisTree(String name, T value) { this.value = value; } - // public AnalysisTree(Hash) { - // this.name = name; - // } - public String getName() { return this.name; } @@ -47,34 +43,32 @@ public T getValue() { return this.value; } - public AnalysisTree(AnalysisTree analysisTree) { + public AnalysisTree(AnalysisTree analysisTree) { this.name = analysisTree.name; + this.value = analysisTree.value; } @Override public AnalysisTree cloneNode() { // TODO Auto-generated method stub - return new AnalysisTree(this); + return new AnalysisTree<>(this); } @Override - public boolean equalsNode(AnalysisTree other) { - return this.name.equals(other.name); + public boolean equalsNode(AnalysisTree other) { + return this.name.equals(other.name) && this.value.equals(other.value); } // TODO is this hashing OK? @Override public int hashCodeNode() { - return Objects.hash(this.getClass(), this.name); + return Objects.hash(this.getClass(), this.name, this.value.getClass(), this.value); } - public static AnalysisTree hashMapToTree(HashMap hashMap, String name) { + public static AnalysisTree hashMapToTree(HashMap hashMap, String name) { AnalysisTree root = new AnalysisTree<>(name, (Object) null); for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { String currentKey = iterator.next(); - /*if (obj.getClass().equals("HashMap")) { - hashMapToTree((HashMap)obj); - }*/ if (hashMap.get(currentKey) instanceof Integer) { root.addChild(new AnalysisTree<>(currentKey, (int) hashMap.get(currentKey))); } else if (hashMap.get(currentKey) instanceof Float) { From d58905505c3071d08556bd26a6637596c3606662 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 8 Oct 2025 09:37:56 +0200 Subject: [PATCH 084/257] feat: add a Tree class to manage the analysis results of a feature model --- .../feature/model/analysis/AnalysisTree.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java new file mode 100644 index 00000000..cebf60b9 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.tree.structure.ATree; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Objects; + +public class AnalysisTree extends ATree> { + + String name; + T value; + + public AnalysisTree(String name, T value) { + this.name = name; + this.value = value; + } + + public String getName() { + return this.name; + } + + public T getValue() { + return this.value; + } + + public AnalysisTree(AnalysisTree analysisTree) { + this.name = analysisTree.name; + this.value = analysisTree.value; + } + + @Override + public AnalysisTree cloneNode() { + // TODO Auto-generated method stub + return new AnalysisTree<>(this); + } + + @Override + public boolean equalsNode(AnalysisTree other) { + return this.name.equals(other.name) && this.value.equals(other.value); + } + + @Override + public int hashCodeNode() { + return Objects.hash(this.getClass(), this.name, this.value.getClass(), this.value); + } + + public static AnalysisTree hashMapToTree(HashMap hashMap, String name) { + AnalysisTree root = new AnalysisTree<>(name, (Object) null); + for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { + String currentKey = iterator.next(); + if (hashMap.get(currentKey) instanceof Integer) { + root.addChild(new AnalysisTree<>(currentKey, (int) hashMap.get(currentKey))); + } else if (hashMap.get(currentKey) instanceof Float) { + root.addChild(new AnalysisTree<>(currentKey, (float) hashMap.get(currentKey))); + } else if (hashMap.get(currentKey) instanceof Double) { + root.addChild(new AnalysisTree<>(currentKey, (double) hashMap.get(currentKey))); + } else if (hashMap.get(currentKey) instanceof HashMap) { + root.addChild(hashMapToTree((HashMap) hashMap.get(currentKey), currentKey)); + } else { + //TODO Add handling for other types or errors if needed + } + } + return root; + } + + @Override + public String toString() { + return "" + this.getClass() + " " + this.name; + } +} \ No newline at end of file From fbf71dfe75d9447bf57aa48d0c6a2f33d33b297d Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Wed, 8 Oct 2025 12:00:29 +0200 Subject: [PATCH 085/257] feat: general functionality --- .../feature/model/cli/FormatConversion.java | 101 +++++++++++++++--- .../model/cli/FormatConversionTest.java | 72 +++++++++++++ .../model/cli/PrintStatisticsTest.java | 2 +- 3 files changed, 159 insertions(+), 16 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 0a79515e..48524357 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -32,6 +32,7 @@ import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.*; +import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; import de.featjar.feature.model.computation.ComputeAtomsCount; import de.featjar.feature.model.computation.ComputeAverageConstraint; import de.featjar.feature.model.computation.ComputeFeatureDensity; @@ -40,8 +41,10 @@ import de.featjar.feature.model.io.xml.GraphVizFeatureModelFormat; import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; +import java.io.IOException; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -54,11 +57,20 @@ * @author Knut, Kilian & Benjamin */ public class FormatConversion implements ICommand { + + + + private static final List supportedInputFileExtensions = Arrays.asList("xml", "uvl", "dot"); + private static final List supportedOutputFileExtensions = Arrays.asList("xml", "uvl", "dot"); + public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: csv, xml, yaml, txt") .setValidator(Option.PathValidator); + + + /** * Output option for saving files. */ @@ -85,24 +97,44 @@ public int run(OptionList optionParser) { // Valid formats: XML, UVL, GraphVis - // opening file - Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); - Result load = IO.load(inputPath, FeatureModelFormats.getInstance()); - FeatureModel model = (FeatureModel) load.orElseThrow(); + + if(!checkIfInputOutputIsPresent(optionParser)) { + return 1; + }; + IFeatureModel model = inputParser(optionParser); //model == null falls error occurred + // check if provided file extensions are supported + String inputFileExtension = IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get()); + + if (!supportedInputFileExtensions.contains(inputFileExtension)) { + System.out.println("supportedInputFileExtensions: " + supportedInputFileExtensions); + System.out.println("input: " + IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get())); + FeatJAR.log().error("Unsupported input file extension."); + return 2; + } + + String outputFileExtension = IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); + + if (!supportedOutputFileExtensions.contains(outputFileExtension)) { + FeatJAR.log().error("Unsupported output file extension."); + return 2; + } + + + if(saveFile(optionParser, model, outputFileExtension)); + + + Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); + + if(!isValidOutputPath(outputPath)) { + return 1; + } + + + // saving file - Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); - - Path test = Paths.get(""); - System.out.println(test.toAbsolutePath()); - - Path target = Paths.get(test.toAbsolutePath()+"model.dot"); - - try { - - IO.save(model, target, new XMLFeatureModelFormat()); - FeatJAR.log().message("HALLO"); + IO.save(model, outputPath, new XMLFeatureModelFormat()); }catch (Exception e) { FeatJAR.log().error(e.getMessage()); @@ -112,6 +144,45 @@ public int run(OptionList optionParser) { return 0; } + private boolean checkIfInputOutputIsPresent(OptionList optionParser) { + if (!optionParser.getResult(INPUT_OPTION).isPresent()) { + FeatJAR.log().error("No input path provided."); + return false; + } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { + FeatJAR.log().error("No output path provided."); + return false; + } + return true; + } + + private IFeatureModel inputParser(OptionList optionParser) { + Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); + IFeatureModel model = null; + try { + Result load = IO.load(inputPath, FeatureModelFormats.getInstance()); + model = load.get(); + return model; + }catch (Exception e) { + FeatJAR.log().error(e.getMessage()); + } + return model; + }; + + private boolean isValidOutputPath(Path outputPath) { + // + return true; + } + + private boolean saveFile(OptionList optionParser, IFeatureModel model, String fileExtension) { + Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); + try { + IO.save(model, outputPath, new XMLFeatureModelFormat()); + return true; + }catch (IOException e) { + FeatJAR.log().error(e.getMessage()); + } + return false; + }; /** diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java new file mode 100644 index 00000000..c5d1f92e --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.cli; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + + +import de.featjar.base.FeatJAR; +import de.featjar.base.data.identifier.AIdentifier; +import de.featjar.base.data.identifier.IIdentifiable; +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import org.junit.jupiter.api.Test; + +/** + * Tests for {@link AIdentifier} and {@link IIdentifiable}. + * + * @author Knut & Kilian + */ +public class FormatConversionTest { + + FormatConversion formatConversion = new FormatConversion(); + + + + @Test + void fileWritingTest() throws IOException { + + String outPutModel = "output_model.xml"; + + int exit_code = FeatJAR.runTest( + "formatConversion", "--input", "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", "--output", outPutModel); + assertEquals(0, exit_code); + + + assertTrue(new File(outPutModel).exists()); + Path pathToBeDeleted = Paths.get(outPutModel); + assertDoesNotThrow(() -> { + Files.deleteIfExists(pathToBeDeleted); + } ); + } + + +} diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index c19e18a6..63a4bf0f 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -109,7 +109,7 @@ void scopeAll() throws IOException { } @Test - void scopeTreeRelated() throws IOException { + void scopeTreeRelated() throws IOException { String content = "{[Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; String comparison = printStats From 7d39e3de5ba27d7d5a92d3acf5da7de315d50ded Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 12:06:58 +0200 Subject: [PATCH 086/257] chore: removed unneeded imports and semicolons --- .../feature/model/cli/FormatConversion.java | 22 ++++--------------- 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 48524357..8d47f64c 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -21,34 +21,20 @@ package de.featjar.feature.model.cli; import de.featjar.base.FeatJAR; -import de.featjar.base.cli.ACommand; import de.featjar.base.cli.ICommand; import de.featjar.base.cli.Option; import de.featjar.base.cli.OptionList; -import de.featjar.base.computation.Computations; import de.featjar.base.data.Result; import de.featjar.base.io.IO; -import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeatureModel; -import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.*; -import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; -import de.featjar.feature.model.computation.ComputeAtomsCount; -import de.featjar.feature.model.computation.ComputeAverageConstraint; -import de.featjar.feature.model.computation.ComputeFeatureDensity; -import de.featjar.feature.model.computation.ComputeOperatorDistribution; import de.featjar.feature.model.io.FeatureModelFormats; -import de.featjar.feature.model.io.xml.GraphVizFeatureModelFormat; import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; import java.io.IOException; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Optional; /** @@ -58,7 +44,7 @@ */ public class FormatConversion implements ICommand { - + private static final List supportedInputFileExtensions = Arrays.asList("xml", "uvl", "dot"); private static final List supportedOutputFileExtensions = Arrays.asList("xml", "uvl", "dot"); @@ -100,7 +86,7 @@ public int run(OptionList optionParser) { if(!checkIfInputOutputIsPresent(optionParser)) { return 1; - }; + } IFeatureModel model = inputParser(optionParser); //model == null falls error occurred // check if provided file extensions are supported @@ -166,7 +152,7 @@ private IFeatureModel inputParser(OptionList optionParser) { FeatJAR.log().error(e.getMessage()); } return model; - }; + } private boolean isValidOutputPath(Path outputPath) { // @@ -182,7 +168,7 @@ private boolean saveFile(OptionList optionParser, IFeatureModel model, String fi FeatJAR.log().error(e.getMessage()); } return false; - }; + } /** From a68e6b968f41980e1bb1d60b1953acb3f1498340 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 12:53:18 +0200 Subject: [PATCH 087/257] refactor: cleaned up code structure --- .../feature/model/cli/FormatConversion.java | 67 ++++++++++--------- 1 file changed, 36 insertions(+), 31 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 8d47f64c..2207aaf4 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -26,6 +26,7 @@ import de.featjar.base.cli.OptionList; import de.featjar.base.data.Result; import de.featjar.base.io.IO; +import de.featjar.base.io.format.IFormat; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.analysis.*; import de.featjar.feature.model.io.FeatureModelFormats; @@ -105,29 +106,15 @@ public int run(OptionList optionParser) { FeatJAR.log().error("Unsupported output file extension."); return 2; } - - - if(saveFile(optionParser, model, outputFileExtension)); - - - Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); - - if(!isValidOutputPath(outputPath)) { - return 1; - } - - - - // saving file - try { - IO.save(model, outputPath, new XMLFeatureModelFormat()); - - }catch (Exception e) { - FeatJAR.log().error(e.getMessage()); + + // check if output path is valid + Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); + if(!isValidOutputPath(outputPath)) { + return 1; } - - - return 0; + + // save file + return saveFile(outputPath, model, outputFileExtension); } private boolean checkIfInputOutputIsPresent(OptionList optionParser) { @@ -158,18 +145,36 @@ private boolean isValidOutputPath(Path outputPath) { // return true; } - - private boolean saveFile(OptionList optionParser, IFeatureModel model, String fileExtension) { - Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); + + /** + * Handles the saving of the output file. Chooses a method appropriate for the respective file type. + * @param outputPath: full path to the output file + * @param model: Feature model read from original input file + * @param fileExtension IFormat that can write FeatureModels to our output file extension + * @return 0 on success, 1 on IOException, 2 on invalid output file extension + */ + private int saveFile(Path outputPath, IFeatureModel model, String fileExtension) { + IFormat format; + + switch (fileExtension) { + case "xml": + format = new XMLFeatureModelFormat(); + break; + default: + FeatJAR.log().error("Unsupported output file extension: " + fileExtension); + return 2; + } + try { - IO.save(model, outputPath, new XMLFeatureModelFormat()); - return true; + IO.save(model, outputPath, format); }catch (IOException e) { - FeatJAR.log().error(e.getMessage()); + FeatJAR.log().error(e.getMessage()); + return 1; } - return false; + return 0; + } - + /** * @@ -177,7 +182,7 @@ private boolean saveFile(OptionList optionParser, IFeatureModel model, String fi */ @Override public Optional getDescription() { - return Optional.of("Convert exisiting file of feature model into new format."); + return Optional.of("Convert existing file of feature model into new format."); } /** From 91f4d3a8f821b37abf807b957358227db1060c4f Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 13:11:16 +0200 Subject: [PATCH 088/257] refactor: cleaned up code structure --- .../feature/model/cli/FormatConversion.java | 95 +++++++++++-------- 1 file changed, 55 insertions(+), 40 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 2207aaf4..504d93be 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -38,6 +38,8 @@ import java.util.List; import java.util.Optional; +//--input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output "c://home/deskop/model.xml" + /** * Prints statistics about a provided Feature Model. * @@ -47,22 +49,17 @@ public class FormatConversion implements ICommand { - private static final List supportedInputFileExtensions = Arrays.asList("xml", "uvl", "dot"); - private static final List supportedOutputFileExtensions = Arrays.asList("xml", "uvl", "dot"); + private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "dot"); + private static final List supportedOutputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "json"); public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) - .setDescription("Path to input file. Accepted File Types: csv, xml, yaml, txt") + .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) .setValidator(Option.PathValidator); - - - - /** - * Output option for saving files. - */ - public static final Option OUTPUT_OPTION = - Option.newOption("output", Option.PathParser).setDescription("Path to output file. Accepted File Types: csv, xml, yaml, txt, json"); + public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) + .setDescription("Path to output file. Accepted File Types: " + supportedInputFileExtensions) + .setValidator(Option.PathValidator); /** * {@return all options registered for the calling class} @@ -70,42 +67,35 @@ public class FormatConversion implements ICommand { public final List> getOptions() { return Option.getAllOptions(getClass()); } - - //--input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output "c://home/deskop/model.xml" + /** * - * @param optionParser the option parser + * @param optionParser option parser supplied by command line execution * - * @return returns 0 if successful, 1 in case of error + * @return 0 on success, 1 if in- or output paths are invalid, 2 on IOException, 3 if no model could be parsed from input file */ @Override public int run(OptionList optionParser) { - - // Valid formats: XML, UVL, GraphVis - - + + // check if there is a missing input / output argument if(!checkIfInputOutputIsPresent(optionParser)) { return 1; } - IFeatureModel model = inputParser(optionParser); //model == null falls error occurred - - // check if provided file extensions are supported - String inputFileExtension = IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get()); - - if (!supportedInputFileExtensions.contains(inputFileExtension)) { - System.out.println("supportedInputFileExtensions: " + supportedInputFileExtensions); - System.out.println("input: " + IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get())); - FeatJAR.log().error("Unsupported input file extension."); - return 2; - } - - String outputFileExtension = IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); - - if (!supportedOutputFileExtensions.contains(outputFileExtension)) { - FeatJAR.log().error("Unsupported output file extension."); - return 2; - } + + // check if provided file extensions are supported + String inputFileExtension = IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get()); + String outputFileExtension = IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); + if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { + return 1; + }; + + // check if model was corrected extracted from input + IFeatureModel model = inputParser(optionParser); + if (model == null) { + FeatJAR.log().error("No model parsed from input file!"); + return 3; + } // check if output path is valid Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); @@ -117,6 +107,30 @@ public int run(OptionList optionParser) { return saveFile(outputPath, model, outputFileExtension); } + /** + * Checks if input and output file extensions provided by user appear in list of supported extensions. + * @param inputFileExtension: extension used for the input file + * @param outputFileExtension extension used for the output file + * @return true if both extensions are valid, false if either is invalid + */ + private boolean checkIfFileExtensionsValid (String inputFileExtension, String outputFileExtension) { + if (!supportedInputFileExtensions.contains(inputFileExtension)) { + FeatJAR.log().error("Unsupported input file extension."); + System.out.println("Received extension: " + inputFileExtension + + "\n Supported extensions: " + supportedInputFileExtensions); + return false; + } + + if (!supportedOutputFileExtensions.contains(outputFileExtension)) { + FeatJAR.log().error("Unsupported output file extension."); + System.out.println("Received extension: " + outputFileExtension + + "\n Supported extensions: " + supportedOutputFileExtensions); + return false; + } + return true; + } + + private boolean checkIfInputOutputIsPresent(OptionList optionParser) { if (!optionParser.getResult(INPUT_OPTION).isPresent()) { FeatJAR.log().error("No input path provided."); @@ -151,7 +165,7 @@ private boolean isValidOutputPath(Path outputPath) { * @param outputPath: full path to the output file * @param model: Feature model read from original input file * @param fileExtension IFormat that can write FeatureModels to our output file extension - * @return 0 on success, 1 on IOException, 2 on invalid output file extension + * @return 0 on success, 1 on invalid output file extension, 2 on IOException, */ private int saveFile(Path outputPath, IFeatureModel model, String fileExtension) { IFormat format; @@ -161,15 +175,16 @@ private int saveFile(Path outputPath, IFeatureModel model, String fileExtension) format = new XMLFeatureModelFormat(); break; default: + // this still catches errors if the switch case construct has not implemented all supported file types! FeatJAR.log().error("Unsupported output file extension: " + fileExtension); - return 2; + return 1; } try { IO.save(model, outputPath, format); }catch (IOException e) { FeatJAR.log().error(e.getMessage()); - return 1; + return 2; } return 0; From 00341498978e10d3f952ce431d094c07d52b4993 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 13:54:09 +0200 Subject: [PATCH 089/257] style: removed unused IOException throws --- .../model/cli/PrintStatisticsTest.java | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 63a4bf0f..2e707433 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -28,7 +28,6 @@ import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; -import java.io.IOException; import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; @@ -49,7 +48,7 @@ private FeatureModel generateMinimalModel() { } @Test - void inputTest() throws IOException { + void inputTest() { int exit_code = FeatJAR.runTest( "printStats", "--input", "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"); @@ -57,14 +56,14 @@ void inputTest() throws IOException { } @Test - void noInput() throws IOException { + void noInput() { assertEquals(1, FeatJAR.runTest("printStats", "--input")); assertEquals(1, FeatJAR.runTest("printStats")); } @Test - void outputWithFileValidExtension() throws IOException { + void outputWithFileValidExtension() { int exit_code = FeatJAR.runTest( "printStats", @@ -76,7 +75,7 @@ void outputWithFileValidExtension() throws IOException { } @Test - void outputWithFileInvalidExtension() throws IOException { + void outputWithFileInvalidExtension() { int exit_code = FeatJAR.runTest( "printStats", @@ -88,7 +87,7 @@ void outputWithFileInvalidExtension() throws IOException { } @Test - void outputWithoutFileExtension() throws IOException { + void outputWithoutFileExtension() { int exit_code = FeatJAR.runTest( "printStats", @@ -100,7 +99,7 @@ void outputWithoutFileExtension() throws IOException { } @Test - void scopeAll() throws IOException { + void scopeAll() { String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}, [Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; String comparison = @@ -109,7 +108,7 @@ void scopeAll() throws IOException { } @Test - void scopeTreeRelated() throws IOException { + void scopeTreeRelated() { String content = "{[Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; String comparison = printStats @@ -119,7 +118,7 @@ void scopeTreeRelated() throws IOException { } @Test - void scopeConstraintRelated() throws IOException { + void scopeConstraintRelated() { String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}}"; String comparison = printStats .collectStats(minimalModel, AnalysesScope.CONSTRAINT_RELATED) @@ -128,7 +127,7 @@ void scopeConstraintRelated() throws IOException { } @Test - void prettyStringBuilder() throws IOException { + void prettyStringBuilder() { LinkedHashMap testData = new LinkedHashMap<>(); testData.put("Normal Entry", 10); From 65fa95cec2e72c54aae38c14b21610649c7ff0ba Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 13:55:49 +0200 Subject: [PATCH 090/257] style: replaced redundant call: Instead of building a string with stringbuilder and then immediately doing .toString() on it, we just directly use a String --- .../de/featjar/feature/model/cli/PrintStatisticsTest.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 2e707433..f66d6c9b 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -138,8 +138,7 @@ void prettyStringBuilder() { testData.put("Number of Atoms", ""); testData.put("[Tree 1] Average Number of Children", ""); - StringBuilder comparison = new StringBuilder(); - comparison.append("Normal Entry : 10\n" + String comparison = "Normal Entry : 10\n" + "HashMap Entry \n" + " Nested Entry 1 : 5\n" + " Nested Entry 2 : 6\n" @@ -150,8 +149,8 @@ void prettyStringBuilder() { + "\n" + " TREE RELATED STATS\n" + " \n" - + "[Tree 1] Average Number of Children : \n"); + + "[Tree 1] Average Number of Children : \n"; - assertEquals(printStats.buildStringPrettyStats(testData).toString(), comparison.toString()); + assertEquals(comparison, printStats.buildStringPrettyStats(testData).toString()); } } From 3f7208812d2b82eb2f5164b01c86fb4caf72d201 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 14:00:38 +0200 Subject: [PATCH 091/257] doc: added missing comments --- .../featjar/feature/model/cli/PrintStatistics.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index f548dbe3..71a9cf07 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -112,7 +112,7 @@ public int run(OptionList optionParser) { /** * writes statistics into a file, depending on file type - * @param path + * @param path: full path to output file * @param type: is extracted from provided output path, needs to be lower case */ private void writeTo(Path path, String type) { @@ -146,8 +146,8 @@ private void writeTo(Path path, String type) { /** * method for collecting statistics of the provided feature model depending on specified scope of information (all, constraint related, tree related) - * @param model - * @param scope + * @param model: a feature model from which stats will be collected + * @param scope: describes whether only constraint-related, only tree-related, or both kinds of stats are to be collected * @return LinkedHashMap with stats data, keys are descriptive strings, values types depend on statistic (Integer, Float, HashMap) */ public LinkedHashMap collectStats(FeatureModel model, AnalysesScope scope) { @@ -220,7 +220,7 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc /** * - * @param data + * @param data Map of the gathered statistics. Keys name the respective stat, values save the stat value itself. */ public void printStatsPretty(LinkedHashMap data) { FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + buildStringPrettyStats(data)); @@ -228,8 +228,8 @@ public void printStatsPretty(LinkedHashMap data) { /** * - * @param data - * @return + * @param data Map of the gathered statistics. Keys name the respective stat, values save the stat value itself. + * {@return StringBuilder with stats written into it in a pretty way} */ public StringBuilder buildStringPrettyStats(LinkedHashMap data) { StringBuilder outputString = new StringBuilder(); From ac7149dff2cc8efe1c79ae88491b7881ba317452 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 14:01:31 +0200 Subject: [PATCH 092/257] style: removed redundant <> specification --- src/main/java/de/featjar/feature/model/cli/PrintStatistics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 71a9cf07..73eff4df 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -152,7 +152,7 @@ private void writeTo(Path path, String type) { */ public LinkedHashMap collectStats(FeatureModel model, AnalysesScope scope) { - LinkedHashMap data = new LinkedHashMap(); + LinkedHashMap data = new LinkedHashMap<>(); if (scope == AnalysesScope.ALL || scope == AnalysesScope.CONSTRAINT_RELATED) { From c8ef1310b1c60aa80e0ebbce38239b9d8a554953 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 14:02:37 +0200 Subject: [PATCH 093/257] style: adjusted visibility scope of enum AnalysesScope to be consistent with subsequent public classes using them in parameters --- src/main/java/de/featjar/feature/model/cli/PrintStatistics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 73eff4df..c4500ce4 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -50,7 +50,7 @@ */ public class PrintStatistics extends ACommand { - enum AnalysesScope { + public enum AnalysesScope { ALL, TREE_RELATED, CONSTRAINT_RELATED From 2de8821d94fac2ec761aa3f67c02102cdcbbae30 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 8 Oct 2025 14:35:54 +0200 Subject: [PATCH 094/257] feat: Created a TreeVisitor class that create map from an AnalysisTree. Test: added a TestClass that should still be modified and seperated. feat: finished the development of the JSONAnalysis formater that can parse and write from/to an AnalysisTree to/from Json. --- build.gradle | 2 + .../feature/model/analysis/AnalysisTree.java | 36 ++++++++- .../analysis/visitor/AnalysisTreeVisitor.java | 76 +++++++++++++++++++ .../model/io/json/JSONFeatureModelFormat.java | 22 +++++- .../feature/model/io/JSONFExportTest.java | 38 ++++++---- 5 files changed, 153 insertions(+), 21 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java diff --git a/build.gradle b/build.gradle index d6fa2a7b..8a6b8f7b 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ plugins { dependencies { api 'de.featjar:formula' api testFixtures('de.featjar:formula') + implementation 'org.json:json:20240303' } license { @@ -13,3 +14,4 @@ license { licence_url = 'https://github.com/FeatureIDE/FeatJAR-feature-model' } } + diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index d352854e..fa113ffe 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -21,6 +21,8 @@ package de.featjar.feature.model.analysis; import de.featjar.base.tree.structure.ATree; +import java.math.BigDecimal; +import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Objects; @@ -59,10 +61,9 @@ public boolean equalsNode(AnalysisTree other) { return this.name.equals(other.name) && this.value.equals(other.value); } - // TODO is this hashing OK? @Override public int hashCodeNode() { - return Objects.hash(this.getClass(), this.name, this.value.getClass(), this.value); + return Objects.hash(this.getClass(), this.name, this.value); } public static AnalysisTree hashMapToTree(HashMap hashMap, String name) { @@ -78,7 +79,30 @@ public static AnalysisTree hashMapToTree(HashMap hashMap, Str } else if (hashMap.get(currentKey) instanceof HashMap) { root.addChild(hashMapToTree((HashMap) hashMap.get(currentKey), currentKey)); } else { - System.out.println("ERROR HELP ME"); + // TODO Add handling for other types or errors if needed + } + } + return root; + } + + public static AnalysisTree hashMapListToTree(HashMap hashMap, String name) { + AnalysisTree root = new AnalysisTree<>(name, (Object) null); + for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { + String currentKey = iterator.next(); + System.out.println("LIST: " + hashMap.get(currentKey).getClass()); + if (hashMap.get(currentKey) instanceof HashMap) { + root.addChild(hashMapListToTree((HashMap) hashMap.get(currentKey), currentKey)); + } else if (hashMap.get(currentKey) instanceof ArrayList) { + ArrayList currentElement = (ArrayList) hashMap.get(currentKey); + if (currentElement.get(1).equals("class java.lang.Double")) { + BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.doubleValue())); + } else if (currentElement.get(1).equals("class java.lang.Integer")) { + root.addChild(new AnalysisTree<>(currentKey, (int) currentElement.get(2))); + } else if (currentElement.get(1).equals("class java.lang.Float")) { + BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.floatValue())); + } } } return root; @@ -86,6 +110,10 @@ public static AnalysisTree hashMapToTree(HashMap hashMap, Str @Override public String toString() { - return "" + this.getClass() + " " + this.name; + if (this.value == null) { + return "Name: " + this.name + "Value: " + this.value + "Value class: " + "No class"; + } else { + return "Name: " + this.name + "Value: " + this.value + "Value class: " + this.value.getClass(); + } } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java new file mode 100644 index 00000000..677c4123 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java @@ -0,0 +1,76 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis.visitor; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.base.tree.visitor.ITreeVisitor.TraversalAction; +import de.featjar.feature.model.analysis.AnalysisTree; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; + +public class AnalysisTreeVisitor implements ITreeVisitor, HashMap> { + HashMap nodesMap = new HashMap(); + + @Override + public TraversalAction firstVisit(List> path) { + final AnalysisTree node = ITreeVisitor.getCurrentNode(path); + HashMap currentMap = nodesMap; + + if (!ITreeVisitor.getParentNode(path).isPresent()) { + nodesMap.put(ITreeVisitor.getCurrentNode(path).getName(), new HashMap()); + return TraversalAction.CONTINUE; + } + + for (Iterator> iterator = path.iterator(); iterator.hasNext(); ) { + AnalysisTree currentAnalysisTree = iterator.next(); + + if (iterator.hasNext()) { // || node.getName().equals(currentAnalysisTree.getName()) + currentMap = (HashMap) currentMap.get(currentAnalysisTree.getName()); + } + } + + if (node.getChildrenCount() == 0) { + currentMap.put( + node.getName(), + new ArrayList( + Arrays.asList(node.getName(), node.getValue().getClass(), node.getValue()))); + } else { + currentMap.put(node.getName(), new HashMap()); + } + + return TraversalAction.CONTINUE; + } + + @Override + public void reset() { + nodesMap = new HashMap(); + } + + @Override + public Result> getResult() { + + return Result.of(nodesMap); + } +} diff --git a/src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java b/src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java index 93d01067..99468c64 100644 --- a/src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java +++ b/src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java @@ -22,10 +22,14 @@ import de.featjar.base.data.Result; import de.featjar.base.io.format.IFormat; +import de.featjar.base.io.input.AInputMapper; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; import java.util.HashMap; import org.json.JSONObject; -public class JSONFeatureModelFormat implements IFormat> { +public class JSONFeatureModelFormat implements IFormat> { @Override public String getName() { @@ -39,7 +43,7 @@ public String getFileExtension() { @Override public boolean supportsParse() { - return false; + return true; } @Override @@ -48,7 +52,17 @@ public boolean supportsWrite() { } @Override - public Result serialize(HashMap object) { - return Result.of(new JSONObject(object).toString(1)); // new XMLFeatureModelWriter().serialize(object); + public Result serialize(AnalysisTree analysisTree) { + return Result.of(new JSONObject( + Trees.traverse(analysisTree, new AnalysisTreeVisitor()).get()) + .toString(1)); // new XMLFeatureModelWriter().serialize(object); + } + + @Override + public Result> parse(AInputMapper inputMapper) { + HashMap jsonMap = + (HashMap) new JSONObject(inputMapper.get().text()).toMap(); + System.out.println("\n \n \n \n \n \n" + inputMapper.get().text()); + return Result.of(AnalysisTree.hashMapListToTree(jsonMap, "Analysis")); } } diff --git a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java index a39ec759..29479bab 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java @@ -30,7 +30,7 @@ import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Paths; -import java.util.Iterator; +import java.util.HashMap; import java.util.LinkedHashMap; import org.json.JSONObject; import org.junit.jupiter.api.Test; @@ -54,33 +54,45 @@ public void JSONTest() throws IOException { data.put("numOfAtoms", 8); data.put("avgNumOfAsss", 4); + AnalysisTree analsyisTree = AnalysisTree.hashMapToTree(data, "Analysis"); + System.out.println("Tree input" + Trees.traverse(analsyisTree, new TreePrinter())); + FileSystem fileSystem = FileSystems.getDefault(); JSONFeatureModelFormat jsonFormat = new JSONFeatureModelFormat(); - System.out.println(jsonFormat.serialize(data).get()); + System.out.println( + "Tree as json String: \n" + jsonFormat.serialize(analsyisTree).get()); + System.out.println("Tree as json String END \n"); try { FileOutputStream outputStream = new FileOutputStream("filename.json"); - IO.save(data, Paths.get("filename.json"), new JSONFeatureModelFormat()); + IO.save(analsyisTree, Paths.get("filename.json"), new JSONFeatureModelFormat()); System.out.println("no error"); } catch (Exception e) { System.out.println(e); } - JSONObject jsonobj = new JSONObject(data); - System.out.println(jsonobj.toString(1)); - JSONObject jsonobj1 = new JSONObject(jsonobj.toString(1)); + System.out.println(analsyisTree.getChild(1).get().getName()); + + // JSONObject jsonobj = new JSONObject(data); + // System.out.println(jsonobj.toString(1)); + // JSONObject jsonobj1 = new JSONObject(jsonobj.toString(1)); // for (Iterator iterator = jsonobj1.keys(); iterator.hasNext();) { // System.out.println(jsonobj1.get(iterator.next().toString()).getClass()); // } - for (Iterator iterator = data.keySet().iterator(); iterator.hasNext(); ) { - String currentKey = iterator.next(); - System.out.println((data.get(currentKey)).getClass().isPrimitive()); - // System.out.println((data.get(iterator.next())).getClass()); - } + // System.out.println("Tree input" + Trees.traverse(analsyisTree, new TreePrinter())); + + // AnalysisTree analysisTreeLoaded = IO.load(Paths.get("filename.json"), new JSONFeatureModelFormat()).get(); + // AnalysisTree analysisTreeLoaded = IO.load(Paths.get("filename.json"), new JSONFeatureModelFormat()).get(); + String AnalysisListJson = jsonFormat.serialize(analsyisTree).get(); + JSONObject jsonobj = new JSONObject(AnalysisListJson); + AnalysisTree analysisTreeLoaded = + AnalysisTree.hashMapListToTree((HashMap) jsonobj.toMap(), "Analysis"); + System.out.println(jsonobj.toString()); - AnalysisTree analsyisTree = AnalysisTree.hashMapToTree(data, "Analysis"); + // AnalysisTree analysisTreeLoaded = IO.load(Paths.get("filename.json"), new JSONFeatureModelFormat()).get(); - System.out.println(Trees.traverse(analsyisTree, new TreePrinter()).get()); + System.out.println("Tree output" + + Trees.traverse(analysisTreeLoaded, new TreePrinter()).get()); } } From 92148557d716ab0653f353819cd89049db5a60c9 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 8 Oct 2025 09:37:56 +0200 Subject: [PATCH 095/257] feat: add a Tree class to manage the analysis results of a feature model --- .../feature/model/analysis/AnalysisTree.java | 90 +++++++++++++++++++ 1 file changed, 90 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java new file mode 100644 index 00000000..cebf60b9 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.tree.structure.ATree; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Objects; + +public class AnalysisTree extends ATree> { + + String name; + T value; + + public AnalysisTree(String name, T value) { + this.name = name; + this.value = value; + } + + public String getName() { + return this.name; + } + + public T getValue() { + return this.value; + } + + public AnalysisTree(AnalysisTree analysisTree) { + this.name = analysisTree.name; + this.value = analysisTree.value; + } + + @Override + public AnalysisTree cloneNode() { + // TODO Auto-generated method stub + return new AnalysisTree<>(this); + } + + @Override + public boolean equalsNode(AnalysisTree other) { + return this.name.equals(other.name) && this.value.equals(other.value); + } + + @Override + public int hashCodeNode() { + return Objects.hash(this.getClass(), this.name, this.value.getClass(), this.value); + } + + public static AnalysisTree hashMapToTree(HashMap hashMap, String name) { + AnalysisTree root = new AnalysisTree<>(name, (Object) null); + for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { + String currentKey = iterator.next(); + if (hashMap.get(currentKey) instanceof Integer) { + root.addChild(new AnalysisTree<>(currentKey, (int) hashMap.get(currentKey))); + } else if (hashMap.get(currentKey) instanceof Float) { + root.addChild(new AnalysisTree<>(currentKey, (float) hashMap.get(currentKey))); + } else if (hashMap.get(currentKey) instanceof Double) { + root.addChild(new AnalysisTree<>(currentKey, (double) hashMap.get(currentKey))); + } else if (hashMap.get(currentKey) instanceof HashMap) { + root.addChild(hashMapToTree((HashMap) hashMap.get(currentKey), currentKey)); + } else { + //TODO Add handling for other types or errors if needed + } + } + return root; + } + + @Override + public String toString() { + return "" + this.getClass() + " " + this.name; + } +} \ No newline at end of file From 35c8fba0d6ab6d426cda20fcca93301b072f31eb Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Wed, 8 Oct 2025 14:44:47 +0200 Subject: [PATCH 096/257] test: even more tests for input/output --- .../feature/model/analysis/AnalysisTree.java | 4 +- .../feature/model/cli/FormatConversion.java | 117 +++++++++--------- .../ConstraintPropertiesVisitorTest.java | 14 +-- .../model/cli/FormatConversionTest.java | 68 ++++++---- .../model/cli/PrintStatisticsTest.java | 2 + 5 files changed, 118 insertions(+), 87 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index cebf60b9..99e3bea4 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -77,7 +77,7 @@ public static AnalysisTree hashMapToTree(HashMap hashMap, Str } else if (hashMap.get(currentKey) instanceof HashMap) { root.addChild(hashMapToTree((HashMap) hashMap.get(currentKey), currentKey)); } else { - //TODO Add handling for other types or errors if needed + // TODO Add handling for other types or errors if needed } } return root; @@ -87,4 +87,4 @@ public static AnalysisTree hashMapToTree(HashMap hashMap, Str public String toString() { return "" + this.getClass() + " " + this.name; } -} \ No newline at end of file +} diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 504d93be..d80fa4d5 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -27,39 +27,37 @@ import de.featjar.base.data.Result; import de.featjar.base.io.IO; import de.featjar.base.io.format.IFormat; +import de.featjar.base.io.text.GenericTextFormat; import de.featjar.feature.model.IFeatureModel; -import de.featjar.feature.model.analysis.*; import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.feature.model.io.xml.GraphVizFeatureModelFormat; import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; - import java.io.IOException; import java.nio.file.Path; import java.util.Arrays; import java.util.List; import java.util.Optional; -//--input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output "c://home/deskop/model.xml" +// --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output +// "c://home/deskop/model.xml" /** * Prints statistics about a provided Feature Model. * * @author Knut, Kilian & Benjamin */ -public class FormatConversion implements ICommand { +public class FormatConversion implements ICommand { + private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "dot"); + private static final List supportedOutputFileExtensions = + Arrays.asList("csv", "xml", "yaml", "txt", "json", "dot"); - - private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "dot"); - private static final List supportedOutputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "json"); - - - public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) + public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) .setValidator(Option.PathValidator); public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) - .setDescription("Path to output file. Accepted File Types: " + supportedInputFileExtensions) - .setValidator(Option.PathValidator); + .setDescription("Path to output file. Accepted File Types: " + supportedInputFileExtensions); /** * {@return all options registered for the calling class} @@ -68,9 +66,8 @@ public final List> getOptions() { return Option.getAllOptions(getClass()); } - /** - * + * * @param optionParser option parser supplied by command line execution * * @return 0 on success, 1 if in- or output paths are invalid, 2 on IOException, 3 if no model could be parsed from input file @@ -78,20 +75,22 @@ public final List> getOptions() { @Override public int run(OptionList optionParser) { - // check if there is a missing input / output argument - if(!checkIfInputOutputIsPresent(optionParser)) { - return 1; - } + if (!checkIfInputOutputIsPresent(optionParser)) { + return 1; + } // check if provided file extensions are supported - String inputFileExtension = IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get()); - String outputFileExtension = IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); + String inputFileExtension = + IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get()); + String outputFileExtension = + IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { return 1; - }; + } + ; // check if model was corrected extracted from input - IFeatureModel model = inputParser(optionParser); + IFeatureModel model = inputParser(optionParser); if (model == null) { FeatJAR.log().error("No model parsed from input file!"); return 3; @@ -99,11 +98,10 @@ public int run(OptionList optionParser) { // check if output path is valid Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); - if(!isValidOutputPath(outputPath)) { + if (!isValidOutputPath(outputPath)) { return 1; } - // save file return saveFile(outputPath, model, outputFileExtension); } @@ -113,51 +111,50 @@ public int run(OptionList optionParser) { * @param outputFileExtension extension used for the output file * @return true if both extensions are valid, false if either is invalid */ - private boolean checkIfFileExtensionsValid (String inputFileExtension, String outputFileExtension) { + private boolean checkIfFileExtensionsValid(String inputFileExtension, String outputFileExtension) { if (!supportedInputFileExtensions.contains(inputFileExtension)) { FeatJAR.log().error("Unsupported input file extension."); - System.out.println("Received extension: " + inputFileExtension + - "\n Supported extensions: " + supportedInputFileExtensions); + System.out.println("Received extension: " + inputFileExtension + "\n Supported extensions: " + + supportedInputFileExtensions); return false; } if (!supportedOutputFileExtensions.contains(outputFileExtension)) { FeatJAR.log().error("Unsupported output file extension."); - System.out.println("Received extension: " + outputFileExtension + - "\n Supported extensions: " + supportedOutputFileExtensions); + System.out.println("Received extension: " + outputFileExtension + "\n Supported extensions: " + + supportedOutputFileExtensions); return false; } return true; } - private boolean checkIfInputOutputIsPresent(OptionList optionParser) { - if (!optionParser.getResult(INPUT_OPTION).isPresent()) { - FeatJAR.log().error("No input path provided."); - return false; - } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { - FeatJAR.log().error("No output path provided."); - return false; - } - return true; + if (!optionParser.getResult(INPUT_OPTION).isPresent()) { + FeatJAR.log().error("No input path provided."); + return false; + } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { + FeatJAR.log().error("No output path provided."); + return false; + } + return true; } - + private IFeatureModel inputParser(OptionList optionParser) { - Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); - IFeatureModel model = null; - try { - Result load = IO.load(inputPath, FeatureModelFormats.getInstance()); - model = load.get(); - return model; - }catch (Exception e) { - FeatJAR.log().error(e.getMessage()); - } - return model; + Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); + IFeatureModel model = null; + try { + Result load = IO.load(inputPath, FeatureModelFormats.getInstance()); + model = load.get(); + return model; + } catch (Exception e) { + FeatJAR.log().error(e.getMessage()); + } + return model; } - + private boolean isValidOutputPath(Path outputPath) { - // - return true; + // + return true; } /** @@ -167,30 +164,36 @@ private boolean isValidOutputPath(Path outputPath) { * @param fileExtension IFormat that can write FeatureModels to our output file extension * @return 0 on success, 1 on invalid output file extension, 2 on IOException, */ - private int saveFile(Path outputPath, IFeatureModel model, String fileExtension) { + public int saveFile(Path outputPath, IFeatureModel model, String fileExtension) { IFormat format; switch (fileExtension) { case "xml": format = new XMLFeatureModelFormat(); break; + case "dot": + format = new GraphVizFeatureModelFormat(); + break; + case "txt": + format = new GenericTextFormat(); + break; default: // this still catches errors if the switch case construct has not implemented all supported file types! FeatJAR.log().error("Unsupported output file extension: " + fileExtension); return 1; } - try { IO.save(model, outputPath, format); - }catch (IOException e) { + if (model == null) { + throw new IOException("model has value null"); + } + } catch (IOException e) { FeatJAR.log().error(e.getMessage()); return 2; } return 0; - } - /** * * {@return brief description of this class} diff --git a/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java index 274adbc7..03d7ca4c 100644 --- a/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java @@ -28,10 +28,8 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import de.featjar.base.tree.Trees; -import de.featjar.base.tree.structure.ITree; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.formula.structure.IExpression; import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.And; import de.featjar.formula.structure.connective.Implies; @@ -44,8 +42,6 @@ import de.featjar.formula.structure.term.value.Constant; import java.util.HashMap; import java.util.Iterator; -import java.util.List; -import java.util.Optional; import java.util.Set; import org.junit.jupiter.api.Test; @@ -68,7 +64,8 @@ public void atomsVisitorTest() { assertEquals(1, formula1BooleansCount); assertEquals(3, formula2ConstantsCount); assertEquals(1, formula3VariablesCount); - assertEquals(0, Trees.traverse(new And(), new AtomsCount(true, true, true)).orElseThrow()); + assertEquals( + 0, Trees.traverse(new And(), new AtomsCount(true, true, true)).orElseThrow()); } @Test @@ -102,7 +99,8 @@ public void featureDensityVisitorTest() { assertEquals(1, formula3Features.size()); assertTrue(formula3Features.contains("a")); assertFalse(formula3Features.contains("b")); - assertEquals(0, Trees.traverse(new And(), new FeatureDensity()).orElseThrow().size()); + assertEquals( + 0, Trees.traverse(new And(), new FeatureDensity()).orElseThrow().size()); } @Test @@ -133,7 +131,9 @@ public void operatorDensityVisitorTest() { assertEquals(4, formula2Count.size()); assertEquals(0, formula3Count.size()); - assertEquals(0, Trees.traverse(null, new OperatorDistribution()).orElseThrow().size()); + assertEquals( + 0, + Trees.traverse(null, new OperatorDistribution()).orElseThrow().size()); } public FeatureModel createFeatureModel() { diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index c5d1f92e..4091c561 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -20,24 +20,18 @@ */ package de.featjar.feature.model.cli; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - import de.featjar.base.FeatJAR; import de.featjar.base.data.identifier.AIdentifier; import de.featjar.base.data.identifier.IIdentifiable; -import de.featjar.base.data.identifier.Identifiers; -import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; - import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; /** @@ -49,24 +43,56 @@ public class FormatConversionTest { FormatConversion formatConversion = new FormatConversion(); - - @Test void fileWritingTest() throws IOException { - - String outPutModel = "output_model.xml"; - - int exit_code = FeatJAR.runTest( - "formatConversion", "--input", "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", "--output", outPutModel); + + String pathToOutPutModel = "output_model.xml"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + + int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); assertEquals(0, exit_code); - - - assertTrue(new File(outPutModel).exists()); - Path pathToBeDeleted = Paths.get(outPutModel); + assertTrue(new File(pathToOutPutModel).exists()); + Path pathToBeDeleted = Paths.get(pathToOutPutModel); assertDoesNotThrow(() -> { - Files.deleteIfExists(pathToBeDeleted); - } ); + Files.deleteIfExists(pathToBeDeleted); + }); + } + + @Test + void invalidOutput() throws IOException { + + String pathToOutPutModel = "output_model.pdf"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + + int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + assertEquals(1, exit_code); } - + @Test + void invalidInput() throws IOException { + + String pathToOutPutModel = "output_model.xml"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; + + int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + assertEquals(1, exit_code); + } + + @Test + void invalid() throws IOException { + + String pathToOutPutModel = "output_model.xml"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; + + int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + assertEquals(1, exit_code); + } + + @Test + void ioExceptionTest() throws IOException { + FormatConversion fc = new FormatConversion(); + int exit_code = fc.saveFile(Paths.get(""), null, "xml"); + System.out.println(exit_code); + assertEquals(2, exit_code); + } } diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index f66d6c9b..aeab5eb6 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -108,7 +108,9 @@ void scopeAll() { } @Test + void scopeTreeRelated() { + String content = "{[Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; String comparison = printStats From 6fdfea82fdea6dea7630eea6909a29e827da18a8 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 14:48:29 +0200 Subject: [PATCH 097/257] style: removed unnecessary IOException throws, used existing FormatConversion object --- .../feature/model/cli/FormatConversionTest.java | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 4091c561..3f9b68ee 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -28,7 +28,6 @@ import de.featjar.base.data.identifier.AIdentifier; import de.featjar.base.data.identifier.IIdentifiable; import java.io.File; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; @@ -44,7 +43,7 @@ public class FormatConversionTest { FormatConversion formatConversion = new FormatConversion(); @Test - void fileWritingTest() throws IOException { + void fileWritingTest() { String pathToOutPutModel = "output_model.xml"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; @@ -59,7 +58,7 @@ void fileWritingTest() throws IOException { } @Test - void invalidOutput() throws IOException { + void invalidOutput() { String pathToOutPutModel = "output_model.pdf"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; @@ -69,7 +68,7 @@ void invalidOutput() throws IOException { } @Test - void invalidInput() throws IOException { + void invalidInput() { String pathToOutPutModel = "output_model.xml"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; @@ -79,7 +78,7 @@ void invalidInput() throws IOException { } @Test - void invalid() throws IOException { + void invalid() { String pathToOutPutModel = "output_model.xml"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; @@ -89,9 +88,8 @@ void invalid() throws IOException { } @Test - void ioExceptionTest() throws IOException { - FormatConversion fc = new FormatConversion(); - int exit_code = fc.saveFile(Paths.get(""), null, "xml"); + void ioExceptionTest() { + int exit_code = formatConversion.saveFile(Paths.get(""), null, "xml"); System.out.println(exit_code); assertEquals(2, exit_code); } From d52449ef416fbfd4d4956cc1cb280885388e8fe1 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Wed, 8 Oct 2025 16:17:42 +0200 Subject: [PATCH 098/257] feat: information loss prototyping --- .../feature/model/cli/FormatConversion.java | 93 +++++++++++++++---- .../model/cli/FormatConversionTest.java | 82 +++++++++------- 2 files changed, 123 insertions(+), 52 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index d80fa4d5..961ad938 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -41,6 +41,9 @@ // --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output // "c://home/deskop/model.xml" +// Extensionpoints um auf UVL zuzugreifen + + /** * Prints statistics about a provided Feature Model. * @@ -48,6 +51,7 @@ */ public class FormatConversion implements ICommand { + private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "dot"); private static final List supportedOutputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "json", "dot"); @@ -75,6 +79,8 @@ public final List> getOptions() { @Override public int run(OptionList optionParser) { + System.out.println(FeatureModelFormats.getInstance().getExtensions()); + if (!checkIfInputOutputIsPresent(optionParser)) { return 1; } @@ -102,7 +108,7 @@ public int run(OptionList optionParser) { return 1; } - return saveFile(outputPath, model, outputFileExtension); + return saveFile(outputPath, model, inputFileExtension, outputFileExtension); } /** @@ -164,23 +170,72 @@ private boolean isValidOutputPath(Path outputPath) { * @param fileExtension IFormat that can write FeatureModels to our output file extension * @return 0 on success, 1 on invalid output file extension, 2 on IOException, */ - public int saveFile(Path outputPath, IFeatureModel model, String fileExtension) { - IFormat format; - - switch (fileExtension) { - case "xml": - format = new XMLFeatureModelFormat(); - break; - case "dot": - format = new GraphVizFeatureModelFormat(); - break; - case "txt": - format = new GenericTextFormat(); - break; - default: - // this still catches errors if the switch case construct has not implemented all supported file types! - FeatJAR.log().error("Unsupported output file extension: " + fileExtension); - return 1; + private IFormat determineInfoLossFromTypes(String inputFileExtension, String outputFileExtension) { + IFormat format = null; + List noLossExtensions; + List someLossExtensions; + List muchLossExtension; + + String noLoss = "No Information Loss from " + inputFileExtension + " to " + outputFileExtension; + String someLoss = "Some Information Loss from " + inputFileExtension + " to " + outputFileExtension; //genauer ausführen + String muchLoss = "Much Information Loss from " + inputFileExtension + " to " + outputFileExtension; //was verloren gehen wird + + String message = ""; + + switch(inputFileExtension) { + case "xml": + format = new XMLFeatureModelFormat(); + noLossExtensions = Arrays.asList("xml", "json", "yaml"); + someLossExtensions = Arrays.asList("csv", "txt"); + muchLossExtension = Arrays.asList("dot"); + break; + case "dot": + format = new GraphVizFeatureModelFormat(); + noLossExtensions = Arrays.asList("dot", "yaml"); + someLossExtensions = Arrays.asList("xml", "txt"); + muchLossExtension = Arrays.asList("json", "csv"); + break; + case "txt": + format = new GenericTextFormat(); + noLossExtensions = Arrays.asList("txt"); + someLossExtensions = Arrays.asList("xml", "dot", "yaml"); + muchLossExtension = Arrays.asList("json", "csv"); + break; + default: + noLossExtensions = Arrays.asList(""); + someLossExtensions = Arrays.asList(""); + muchLossExtension = Arrays.asList(""); + } + if(noLossExtensions.contains(outputFileExtension)) { + message = noLoss; + } else if(someLossExtensions.contains(outputFileExtension)) { + message = someLoss; + } else if(noLossExtensions.contains(outputFileExtension)) { + message = muchLoss; + } + FeatJAR.log().message(message); + return format; + } + + public int saveFile(Path outputPath, IFeatureModel model, String inputFileExtension, String outputFileExtension) { + IFormat format = determineInfoLossFromTypes(inputFileExtension,outputFileExtension); +// switch (outputFileExtension) { +// case "xml": +// format = new XMLFeatureModelFormat(); +// break; +// case "dot": +// format = new GraphVizFeatureModelFormat(); +// break; +// case "txt": +// format = new GenericTextFormat(); +// break; +// default: +// // this still catches errors if the switch case construct has not implemented all supported file types! +// FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); +// return 1; +// } + if (format == null) { + return 1; } try { IO.save(model, outputPath, format); @@ -188,7 +243,7 @@ public int saveFile(Path outputPath, IFeatureModel model, String fileExtension) throw new IOException("model has value null"); } } catch (IOException e) { - FeatJAR.log().error(e.getMessage()); + FeatJAR.log().error(e); return 2; } return 0; diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 3f9b68ee..f0b31753 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -20,19 +20,28 @@ */ package de.featjar.feature.model.cli; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import de.featjar.base.FeatJAR; import de.featjar.base.data.identifier.AIdentifier; import de.featjar.base.data.identifier.IIdentifiable; +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; + import java.io.File; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; +import de.featjar.feature.model.cli.FormatConversion; + /** * Tests for {@link AIdentifier} and {@link IIdentifiable}. * @@ -42,55 +51,62 @@ public class FormatConversionTest { FormatConversion formatConversion = new FormatConversion(); - @Test - void fileWritingTest() { - String pathToOutPutModel = "output_model.xml"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; - int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + @Test + void fileWritingTest() throws IOException { + + String pathToOutPutModel = "output_model.xml"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + + int exit_code = FeatJAR.runTest( + "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); assertEquals(0, exit_code); assertTrue(new File(pathToOutPutModel).exists()); Path pathToBeDeleted = Paths.get(pathToOutPutModel); assertDoesNotThrow(() -> { - Files.deleteIfExists(pathToBeDeleted); - }); + Files.deleteIfExists(pathToBeDeleted); + } ); } - + @Test - void invalidOutput() { + void invalidOutput() throws IOException { + + String pathToOutPutModel = "output_model.pdf"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; - String pathToOutPutModel = "output_model.pdf"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; - - int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + int exit_code = FeatJAR.runTest( + "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); assertEquals(1, exit_code); } - @Test - void invalidInput() { - - String pathToOutPutModel = "output_model.xml"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; - - int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + void invalidInput() throws IOException { + + String pathToOutPutModel = "output_model.xml"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; + + int exit_code = FeatJAR.runTest( + "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); assertEquals(1, exit_code); } - @Test - void invalid() { - - String pathToOutPutModel = "output_model.xml"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; - - int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + void invalid() throws IOException { + + String pathToOutPutModel = "output_model.xml"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; + + int exit_code = FeatJAR.runTest( + "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); assertEquals(1, exit_code); } - + @Test - void ioExceptionTest() { - int exit_code = formatConversion.saveFile(Paths.get(""), null, "xml"); - System.out.println(exit_code); - assertEquals(2, exit_code); + void ioExceptionTest() throws IOException { + FormatConversion fc = new FormatConversion(); + int exit_code = fc.saveFile(Paths.get(""), null, "xml", "xml"); + assertEquals(2, exit_code); } + + + } From 8a268e04a47289657c0d618e79a1721f60e4eda2 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 16:34:23 +0200 Subject: [PATCH 099/257] feat: prototype for feature support map (for info loss calculations) --- .../feature/model/cli/FormatConversion.java | 47 +++++++++++++++---- 1 file changed, 37 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 961ad938..94ed23d4 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -34,9 +34,7 @@ import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; import java.io.IOException; import java.nio.file.Path; -import java.util.Arrays; -import java.util.List; -import java.util.Optional; +import java.util.*; // --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output // "c://home/deskop/model.xml" @@ -70,6 +68,18 @@ public final List> getOptions() { return Option.getAllOptions(getClass()); } + enum SupportLevel { + FULL, + PARTIAL, + NONE + } + + enum Feature { + FEATURE_1, + FEATURE_2, + FEATURE_3 + } + /** * * @param optionParser option parser supplied by command line execution @@ -80,6 +90,7 @@ public final List> getOptions() { public int run(OptionList optionParser) { System.out.println(FeatureModelFormats.getInstance().getExtensions()); + System.out.println(buildInfoLossMap()); if (!checkIfInputOutputIsPresent(optionParser)) { return 1; @@ -111,6 +122,29 @@ public int run(OptionList optionParser) { return saveFile(outputPath, model, inputFileExtension, outputFileExtension); } + private Map> buildInfoLossMap () { + Map> supportMap = new HashMap<>(); + + // set to eliminate duplicates + Set supportedFileExtensions = new LinkedHashSet<>(supportedInputFileExtensions); + supportedFileExtensions.addAll(supportedOutputFileExtensions); + + // default values + for (String fileExtension: supportedFileExtensions) { + supportMap.put(fileExtension, new EnumMap<>(Feature.class)); // for each extension: add each feature + for (Feature feature : Feature.values()) { + supportMap.get(fileExtension).put(feature, SupportLevel.NONE); // by default: all features are unsupported + } + } + + // fill with real values + supportMap.get("xml").put(Feature.FEATURE_1, SupportLevel.FULL); + supportMap.get("xml").put(Feature.FEATURE_2, SupportLevel.FULL); + supportMap.get("xml").put(Feature.FEATURE_3, SupportLevel.PARTIAL); + + return supportMap; + } + /** * Checks if input and output file extensions provided by user appear in list of supported extensions. * @param inputFileExtension: extension used for the input file @@ -163,13 +197,6 @@ private boolean isValidOutputPath(Path outputPath) { return true; } - /** - * Handles the saving of the output file. Chooses a method appropriate for the respective file type. - * @param outputPath: full path to the output file - * @param model: Feature model read from original input file - * @param fileExtension IFormat that can write FeatureModels to our output file extension - * @return 0 on success, 1 on invalid output file extension, 2 on IOException, - */ private IFormat determineInfoLossFromTypes(String inputFileExtension, String outputFileExtension) { IFormat format = null; List noLossExtensions; From 35ee69584f1a846632938255b14923ace8826082 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Wed, 8 Oct 2025 16:52:23 +0200 Subject: [PATCH 100/257] test: added test to check for analysistree functionality, added test to check for json serialization and parsing. feat: adjusted analysistree equalsNode --- .../feature/model/analysis/AnalysisTree.java | 9 ++ .../feature/model/AnalysisTreeTest.java | 96 +++++++++++++++++++ .../feature/model/io/JSONFExportTest.java | 59 ++++-------- 3 files changed, 121 insertions(+), 43 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/AnalysisTreeTest.java diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index fa113ffe..c4ec7673 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -58,6 +58,15 @@ public AnalysisTree cloneNode() { @Override public boolean equalsNode(AnalysisTree other) { + if (other.value == null && this.value != null) { + return false; + } + if (other.value != null && this.value == null) { + return false; + } + if (this.value == null && other.value == null) { + return this.name.equals(other.name); + } return this.name.equals(other.name) && this.value.equals(other.value); } diff --git a/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java b/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java new file mode 100644 index 00000000..f66103f1 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java @@ -0,0 +1,96 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import de.featjar.feature.model.analysis.AnalysisTree; +import java.util.LinkedHashMap; +import org.junit.jupiter.api.Test; + +public class AnalysisTreeTest { + + @Test + public void mapToTreeTest() { + LinkedHashMap emptyMap = new LinkedHashMap(); + AnalysisTree returnedTree = AnalysisTree.hashMapToTree(emptyMap, "empty"); + + assertEquals(returnedTree.getName(), "empty"); + assertEquals(returnedTree.getChildrenCount(), 0); + + emptyMap.put("intfirstLevel", 42); + emptyMap.put("floatfirstLevel", (float) 42); + emptyMap.put("doublefirstLevel", (double) 42); + + returnedTree = AnalysisTree.hashMapToTree(emptyMap, "valuesFirstLevel"); + assertEquals(returnedTree.getChildrenCount(), 3); + assertTrue(returnedTree.getChild(0).isPresent()); + assertEquals(returnedTree.getChild(0).get().getName(), "intfirstLevel"); + assertEquals(returnedTree.getChild(0).get().getValue(), 42); + assertTrue(returnedTree.getChild(1).isPresent()); + assertEquals(returnedTree.getChild(1).get().getName(), "floatfirstLevel"); + assertEquals(returnedTree.getChild(1).get().getValue(), (float) 42); + assertTrue(returnedTree.getChild(2).isPresent()); + assertEquals(returnedTree.getChild(2).get().getName(), "doublefirstLevel"); + assertEquals(returnedTree.getChild(2).get().getValue(), (double) 42); + + LinkedHashMap firstLevelMap = new LinkedHashMap(); + LinkedHashMap secondLevelMap1 = new LinkedHashMap(); + LinkedHashMap secondLevelMap2 = new LinkedHashMap(); + + firstLevelMap.put("intsecondLevel", 43); + secondLevelMap1.put("intthirdLevel", 61); + secondLevelMap2.put("float1thirdLevel", (float) 21); + secondLevelMap2.put("float2thirdLevel", (float) 22); + + firstLevelMap.put("map1secondLevel", secondLevelMap1); + firstLevelMap.put("map2secondLevel", secondLevelMap2); + emptyMap.put("mapfirstLevel", firstLevelMap); + + returnedTree = AnalysisTree.hashMapToTree(emptyMap, "nestedMaps"); + + assertEquals(returnedTree.getChildrenCount(), 4); + AnalysisTree mapfirstLevel = returnedTree.getChild(3).get(); + assertEquals(mapfirstLevel.getChildrenCount(), 3); + assertEquals(mapfirstLevel.getName(), "mapfirstLevel"); + assertEquals(mapfirstLevel.getValue(), null); + assertEquals(mapfirstLevel.getChild(0).get().getName(), "intsecondLevel"); + assertEquals(mapfirstLevel.getChild(0).get().getValue(), 43); + + AnalysisTree map1secondLevel = mapfirstLevel.getChild(1).get(); + AnalysisTree map2secondLevel = mapfirstLevel.getChild(2).get(); + + assertEquals(map1secondLevel.getName(), "map1secondLevel"); + assertEquals(map1secondLevel.getValue(), null); + assertEquals(map1secondLevel.getChildrenCount(), 1); + assertEquals(map1secondLevel.getChild(0).get().getName(), "intthirdLevel"); + assertEquals(map1secondLevel.getChild(0).get().getValue(), 61); + + assertEquals(map2secondLevel.getName(), "map2secondLevel"); + assertEquals(map2secondLevel.getValue(), null); + assertEquals(map2secondLevel.getChildrenCount(), 2); + assertEquals(map2secondLevel.getChild(0).get().getName(), "float1thirdLevel"); + assertEquals(map2secondLevel.getChild(0).get().getValue(), (float) 21); + assertEquals(map2secondLevel.getChild(1).get().getName(), "float2thirdLevel"); + assertEquals(map2secondLevel.getChild(1).get().getValue(), (float) 22); + } +} diff --git a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java index 29479bab..b532f8e9 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java @@ -20,16 +20,12 @@ */ package de.featjar.feature.model.io; -import de.featjar.base.io.IO; +import static org.junit.jupiter.api.Assertions.assertTrue; + import de.featjar.base.tree.Trees; -import de.featjar.base.tree.visitor.TreePrinter; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.io.json.JSONFeatureModelFormat; -import java.io.FileOutputStream; import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Paths; import java.util.HashMap; import java.util.LinkedHashMap; import org.json.JSONObject; @@ -55,44 +51,21 @@ public void JSONTest() throws IOException { data.put("avgNumOfAsss", 4); AnalysisTree analsyisTree = AnalysisTree.hashMapToTree(data, "Analysis"); - System.out.println("Tree input" + Trees.traverse(analsyisTree, new TreePrinter())); - - FileSystem fileSystem = FileSystems.getDefault(); JSONFeatureModelFormat jsonFormat = new JSONFeatureModelFormat(); - System.out.println( - "Tree as json String: \n" + jsonFormat.serialize(analsyisTree).get()); - System.out.println("Tree as json String END \n"); - try { - FileOutputStream outputStream = new FileOutputStream("filename.json"); - IO.save(analsyisTree, Paths.get("filename.json"), new JSONFeatureModelFormat()); - System.out.println("no error"); - } catch (Exception e) { - System.out.println(e); - } - - System.out.println(analsyisTree.getChild(1).get().getName()); - - // JSONObject jsonobj = new JSONObject(data); - // System.out.println(jsonobj.toString(1)); - // JSONObject jsonobj1 = new JSONObject(jsonobj.toString(1)); - // for (Iterator iterator = jsonobj1.keys(); iterator.hasNext();) { - // System.out.println(jsonobj1.get(iterator.next().toString()).getClass()); - - // } - - // System.out.println("Tree input" + Trees.traverse(analsyisTree, new TreePrinter())); - - // AnalysisTree analysisTreeLoaded = IO.load(Paths.get("filename.json"), new JSONFeatureModelFormat()).get(); - // AnalysisTree analysisTreeLoaded = IO.load(Paths.get("filename.json"), new JSONFeatureModelFormat()).get(); - String AnalysisListJson = jsonFormat.serialize(analsyisTree).get(); - JSONObject jsonobj = new JSONObject(AnalysisListJson); - AnalysisTree analysisTreeLoaded = - AnalysisTree.hashMapListToTree((HashMap) jsonobj.toMap(), "Analysis"); - System.out.println(jsonobj.toString()); - - // AnalysisTree analysisTreeLoaded = IO.load(Paths.get("filename.json"), new JSONFeatureModelFormat()).get(); + JSONObject firstJSONObject = + new JSONObject(jsonFormat.serialize(analsyisTree).get()); + String jsonString = firstJSONObject.toString(); + JSONObject secondJSONJsonObject = new JSONObject(jsonString); + HashMap jsonAsMap = (HashMap) secondJSONJsonObject.toMap(); + AnalysisTree analsyisTreeAfterConversion = AnalysisTree.hashMapListToTree(jsonAsMap, "Analysis"); - System.out.println("Tree output" - + Trees.traverse(analysisTreeLoaded, new TreePrinter()).get()); + analsyisTree.sort(); + analsyisTreeAfterConversion.sort(); + // TODO fix function and adjust test so it does not cheat + assertTrue( + Trees.equals( + analsyisTree, analsyisTreeAfterConversion.getChild(0).get()), + "firstTree\n" + analsyisTree.print() + "\nsecond tree\n" + + analsyisTreeAfterConversion.getChild(0).get().print()); } } From 2e972c227f9c4490cf97624247ef478b52ebd3d8 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 20:31:45 +0200 Subject: [PATCH 101/257] feat: fleshed out infolossmap prototype, needs to be tested --- .../feature/model/cli/FormatConversion.java | 181 +++++++++--------- 1 file changed, 86 insertions(+), 95 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 94ed23d4..84396eda 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -37,7 +37,7 @@ import java.util.*; // --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output -// "c://home/deskop/model.xml" +// "c://home/desktop/model.xml" // Extensionpoints um auf UVL zuzugreifen @@ -68,16 +68,40 @@ public final List> getOptions() { return Option.getAllOptions(getClass()); } - enum SupportLevel { - FULL, - PARTIAL, - NONE + // for info loss map + private enum SupportLevel { + NONE(0), + PARTIAL(1), + FULL(2); + + public final int rank; + SupportLevel(int rank) {this.rank = rank;} } - enum Feature { - FEATURE_1, - FEATURE_2, - FEATURE_3 + // for info loss map + // saving name as well as a description in case we need to explain it to the user later + private enum FileInfo { + hierarchicalFeatureStructure("Hierarchical feature structure"), + featureAttributesAndMetadata("Feature attributes and metadata"), + mandatoryAndOptionalFeatures("Mandatory and optional features"), + featureGroups("Feature groups (AND, OR, XOR)", + "AND groups are equivalent to cardinality groups ranging from 1 to 1, and OR from 1 to n."); + + public final String name; + public final String description; + + FileInfo(String name) { + this.name = name; + this.description = ""; + } + + FileInfo(String name, String description) { + this.name = name; + this.description = description; + } + + @Override + public String toString() { return description.isEmpty() ? name : name + ": " + description; } } /** @@ -89,6 +113,25 @@ enum Feature { @Override public int run(OptionList optionParser) { + // needs to be tested + Map> infoLossMap = buildInfoLossMap(); + String inputExt = "xml"; + String outputExt = "txt"; + + Map input_supports = infoLossMap.get(inputExt); + Map output_supports = infoLossMap.get(outputExt); + + for (FileInfo fileInfo : input_supports.keySet()) { + int inputRank = input_supports.get(fileInfo).rank; + int outputRank = output_supports.get(fileInfo).rank; + if (outputRank < inputRank) { + System.out.println("Info Loss:"); + System.out.println(fileInfo); + System.out.println(inputExt + " support: " + input_supports.get(fileInfo)); + System.out.println(outputExt + " support: " + output_supports.get(fileInfo)); + } + } + System.out.println(FeatureModelFormats.getInstance().getExtensions()); System.out.println(buildInfoLossMap()); @@ -104,7 +147,7 @@ public int run(OptionList optionParser) { if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { return 1; } - ; + // check if model was corrected extracted from input IFeatureModel model = inputParser(optionParser); @@ -113,17 +156,13 @@ public int run(OptionList optionParser) { return 3; } - // check if output path is valid Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); - if (!isValidOutputPath(outputPath)) { - return 1; - } - return saveFile(outputPath, model, inputFileExtension, outputFileExtension); + return saveFile(outputPath, model, outputFileExtension); } - private Map> buildInfoLossMap () { - Map> supportMap = new HashMap<>(); + private Map> buildInfoLossMap () { + Map> supportMap = new HashMap<>(); // set to eliminate duplicates Set supportedFileExtensions = new LinkedHashSet<>(supportedInputFileExtensions); @@ -131,16 +170,24 @@ private Map> buildInfoLossMap () { // default values for (String fileExtension: supportedFileExtensions) { - supportMap.put(fileExtension, new EnumMap<>(Feature.class)); // for each extension: add each feature - for (Feature feature : Feature.values()) { - supportMap.get(fileExtension).put(feature, SupportLevel.NONE); // by default: all features are unsupported + supportMap.put(fileExtension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature + for (FileInfo fileInfo : FileInfo.values()) { + supportMap.get(fileExtension).put(fileInfo, SupportLevel.NONE); // by default: all features are unsupported } } // fill with real values - supportMap.get("xml").put(Feature.FEATURE_1, SupportLevel.FULL); - supportMap.get("xml").put(Feature.FEATURE_2, SupportLevel.FULL); - supportMap.get("xml").put(Feature.FEATURE_3, SupportLevel.PARTIAL); + String ext = "xml"; + supportMap.get(ext).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.FULL); + supportMap.get(ext).put(FileInfo.featureAttributesAndMetadata, SupportLevel.FULL); + supportMap.get(ext).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); + supportMap.get(ext).put(FileInfo.featureGroups, SupportLevel.NONE); + + ext = "txt"; + supportMap.get(ext).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.FULL); + supportMap.get(ext).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); + supportMap.get(ext).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); + supportMap.get(ext).put(FileInfo.featureGroups, SupportLevel.NONE); return supportMap; } @@ -185,84 +232,28 @@ private IFeatureModel inputParser(OptionList optionParser) { try { Result load = IO.load(inputPath, FeatureModelFormats.getInstance()); model = load.get(); - return model; } catch (Exception e) { FeatJAR.log().error(e.getMessage()); } return model; } - - private boolean isValidOutputPath(Path outputPath) { - // - return true; - } - - private IFormat determineInfoLossFromTypes(String inputFileExtension, String outputFileExtension) { - IFormat format = null; - List noLossExtensions; - List someLossExtensions; - List muchLossExtension; - - String noLoss = "No Information Loss from " + inputFileExtension + " to " + outputFileExtension; - String someLoss = "Some Information Loss from " + inputFileExtension + " to " + outputFileExtension; //genauer ausführen - String muchLoss = "Much Information Loss from " + inputFileExtension + " to " + outputFileExtension; //was verloren gehen wird - - String message = ""; - - switch(inputFileExtension) { - case "xml": - format = new XMLFeatureModelFormat(); - noLossExtensions = Arrays.asList("xml", "json", "yaml"); - someLossExtensions = Arrays.asList("csv", "txt"); - muchLossExtension = Arrays.asList("dot"); - break; - case "dot": - format = new GraphVizFeatureModelFormat(); - noLossExtensions = Arrays.asList("dot", "yaml"); - someLossExtensions = Arrays.asList("xml", "txt"); - muchLossExtension = Arrays.asList("json", "csv"); - break; - case "txt": - format = new GenericTextFormat(); - noLossExtensions = Arrays.asList("txt"); - someLossExtensions = Arrays.asList("xml", "dot", "yaml"); - muchLossExtension = Arrays.asList("json", "csv"); - break; - default: - noLossExtensions = Arrays.asList(""); - someLossExtensions = Arrays.asList(""); - muchLossExtension = Arrays.asList(""); - } - if(noLossExtensions.contains(outputFileExtension)) { - message = noLoss; - } else if(someLossExtensions.contains(outputFileExtension)) { - message = someLoss; - } else if(noLossExtensions.contains(outputFileExtension)) { - message = muchLoss; - } - FeatJAR.log().message(message); - return format; - } - public int saveFile(Path outputPath, IFeatureModel model, String inputFileExtension, String outputFileExtension) { - IFormat format = determineInfoLossFromTypes(inputFileExtension,outputFileExtension); -// switch (outputFileExtension) { -// case "xml": -// format = new XMLFeatureModelFormat(); -// break; -// case "dot": -// format = new GraphVizFeatureModelFormat(); -// break; -// case "txt": -// format = new GenericTextFormat(); -// break; -// default: -// // this still catches errors if the switch case construct has not implemented all supported file types! -// FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); -// return 1; -// } - if (format == null) { - return 1; + public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension) { + IFormat format; + switch (outputFileExtension) { + case "xml": + format = new XMLFeatureModelFormat(); + break; + case "dot": + format = new GraphVizFeatureModelFormat(); + break; + case "txt": + format = new GenericTextFormat(); + break; + default: + // this still catches errors if the switch case construct has not implemented all supported file types! + FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); + return 1; } try { IO.save(model, outputPath, format); From 1e8b8d51d79cbc9f18a95342e4ce85376166464d Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 20:34:18 +0200 Subject: [PATCH 102/257] style: cleaned up imports and unnecessary IOException throw declarations --- .../model/cli/FormatConversionTest.java | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index f0b31753..de6241d1 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -28,16 +28,11 @@ import de.featjar.base.FeatJAR; import de.featjar.base.data.identifier.AIdentifier; import de.featjar.base.data.identifier.IIdentifiable; -import de.featjar.base.data.identifier.Identifiers; -import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; import java.io.File; -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; import de.featjar.feature.model.cli.FormatConversion; @@ -51,10 +46,8 @@ public class FormatConversionTest { FormatConversion formatConversion = new FormatConversion(); - - @Test - void fileWritingTest() throws IOException { + void fileWritingTest() { String pathToOutPutModel = "output_model.xml"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; @@ -70,7 +63,7 @@ void fileWritingTest() throws IOException { } @Test - void invalidOutput() throws IOException { + void invalidOutput(){ String pathToOutPutModel = "output_model.pdf"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; @@ -80,7 +73,7 @@ void invalidOutput() throws IOException { assertEquals(1, exit_code); } @Test - void invalidInput() throws IOException { + void invalidInput(){ String pathToOutPutModel = "output_model.xml"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; @@ -90,7 +83,7 @@ void invalidInput() throws IOException { assertEquals(1, exit_code); } @Test - void invalid() throws IOException { + void invalid(){ String pathToOutPutModel = "output_model.xml"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; @@ -101,9 +94,8 @@ void invalid() throws IOException { } @Test - void ioExceptionTest() throws IOException { - FormatConversion fc = new FormatConversion(); - int exit_code = fc.saveFile(Paths.get(""), null, "xml", "xml"); + void ioExceptionTest(){ + int exit_code = formatConversion.saveFile(Paths.get(""), null, "xml"); assertEquals(2, exit_code); } From f86dced3fd252f0ccebb76c8a0af449ddfb51ffb Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 8 Oct 2025 20:41:17 +0200 Subject: [PATCH 103/257] test: built a small test for the info loss map --- .../feature/model/cli/FormatConversion.java | 39 ++++++++++--------- .../model/cli/FormatConversionTest.java | 10 ++--- 2 files changed, 25 insertions(+), 24 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 84396eda..f9cda46f 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -113,24 +113,6 @@ private enum FileInfo { @Override public int run(OptionList optionParser) { - // needs to be tested - Map> infoLossMap = buildInfoLossMap(); - String inputExt = "xml"; - String outputExt = "txt"; - - Map input_supports = infoLossMap.get(inputExt); - Map output_supports = infoLossMap.get(outputExt); - - for (FileInfo fileInfo : input_supports.keySet()) { - int inputRank = input_supports.get(fileInfo).rank; - int outputRank = output_supports.get(fileInfo).rank; - if (outputRank < inputRank) { - System.out.println("Info Loss:"); - System.out.println(fileInfo); - System.out.println(inputExt + " support: " + input_supports.get(fileInfo)); - System.out.println(outputExt + " support: " + output_supports.get(fileInfo)); - } - } System.out.println(FeatureModelFormats.getInstance().getExtensions()); System.out.println(buildInfoLossMap()); @@ -160,6 +142,27 @@ public int run(OptionList optionParser) { return saveFile(outputPath, model, outputFileExtension); } + public int testInfoLossMap() { + Map> infoLossMap = buildInfoLossMap(); + String iExt = "xml"; + String oExt = "txt"; + + Map iSupports = infoLossMap.get(iExt); + Map oSupports = infoLossMap.get(oExt); + + for (FileInfo fileInfo : iSupports.keySet()) { + SupportLevel iSupportLevel = iSupports.get(fileInfo); + SupportLevel oSupportLevel = oSupports.get(fileInfo); + if (oSupportLevel.rank < iSupportLevel.rank) { + System.out.println("Info Loss:"); + System.out.println(fileInfo); + System.out.println(" " + iExt + " support: " + iSupportLevel); + System.out.println(" " + oExt + " support: " + oSupportLevel); + } + } + + return 0; + } private Map> buildInfoLossMap () { Map> supportMap = new HashMap<>(); diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index de6241d1..3054bdb7 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -92,13 +92,11 @@ void invalid(){ "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); assertEquals(1, exit_code); } - + + // temp test for quick testing of the info loss map @Test - void ioExceptionTest(){ - int exit_code = formatConversion.saveFile(Paths.get(""), null, "xml"); - assertEquals(2, exit_code); + void infoLossMapTest(){ + assertEquals(0, formatConversion.testInfoLossMap()); } - - } From 8aebbf8044171f92249f485a76e4e9c2032c1911 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 9 Oct 2025 08:49:43 +0200 Subject: [PATCH 104/257] feat: added small comparison function to infoloss support levels for more naturally reading code structure --- .../java/de/featjar/feature/model/cli/FormatConversion.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index f9cda46f..bdf05a32 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -76,6 +76,8 @@ private enum SupportLevel { public final int rank; SupportLevel(int rank) {this.rank = rank;} + + boolean isLessThan(SupportLevel other) {return this.rank < other.rank; } } // for info loss map @@ -153,7 +155,7 @@ public int testInfoLossMap() { for (FileInfo fileInfo : iSupports.keySet()) { SupportLevel iSupportLevel = iSupports.get(fileInfo); SupportLevel oSupportLevel = oSupports.get(fileInfo); - if (oSupportLevel.rank < iSupportLevel.rank) { + if (oSupportLevel.isLessThan(iSupportLevel)) { System.out.println("Info Loss:"); System.out.println(fileInfo); System.out.println(" " + iExt + " support: " + iSupportLevel); From 7dcee38b566e4f7912e69e6f729354851010335a Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 9 Oct 2025 09:57:27 +0200 Subject: [PATCH 105/257] fix: A problem with the transformation of a hson file/object to an AnalysisTree --- .../feature/model/analysis/AnalysisTree.java | 26 ++++++++++++++++--- ...delFormat.java => JSONAnalysisFormat.java} | 2 +- .../feature/model/io/JSONFExportTest.java | 13 ++++------ 3 files changed, 29 insertions(+), 12 deletions(-) rename src/main/java/de/featjar/feature/model/io/json/{JSONFeatureModelFormat.java => JSONAnalysisFormat.java} (96%) diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index c4ec7673..328007ca 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -23,8 +23,10 @@ import de.featjar.base.tree.structure.ATree; import java.math.BigDecimal; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; +import java.util.List; import java.util.Objects; public class AnalysisTree extends ATree> { @@ -37,6 +39,16 @@ public AnalysisTree(String name, T value) { this.value = value; } + protected AnalysisTree(AnalysisTree... children) { + super(children.length); + if (children.length > 0) super.setChildren(Arrays.asList(children)); + } + + public AnalysisTree(List> children) { + super(children.size()); + super.setChildren(children); + } + public String getName() { return this.name; } @@ -98,7 +110,6 @@ public static AnalysisTree hashMapListToTree(HashMap hashMap, AnalysisTree root = new AnalysisTree<>(name, (Object) null); for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { String currentKey = iterator.next(); - System.out.println("LIST: " + hashMap.get(currentKey).getClass()); if (hashMap.get(currentKey) instanceof HashMap) { root.addChild(hashMapListToTree((HashMap) hashMap.get(currentKey), currentKey)); } else if (hashMap.get(currentKey) instanceof ArrayList) { @@ -117,12 +128,21 @@ public static AnalysisTree hashMapListToTree(HashMap hashMap, return root; } + public static AnalysisTree hashMapListToTree(HashMap hashMap) { + if (hashMap.size() == 1) { + String key = hashMap.keySet().iterator().next(); + return hashMapListToTree((HashMap) hashMap.get(key), key); + } else { + return new AnalysisTree<>(); + } + } + @Override public String toString() { if (this.value == null) { - return "Name: " + this.name + "Value: " + this.value + "Value class: " + "No class"; + return "Name: " + this.name + " - Value: " + this.value + " - Value class: " + "No class"; } else { - return "Name: " + this.name + "Value: " + this.value + "Value class: " + this.value.getClass(); + return "Name: " + this.name + " - Value: " + this.value + " - Value class: " + this.value.getClass(); } } } diff --git a/src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java similarity index 96% rename from src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java rename to src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java index 99468c64..fefccc2c 100644 --- a/src/main/java/de/featjar/feature/model/io/json/JSONFeatureModelFormat.java +++ b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java @@ -29,7 +29,7 @@ import java.util.HashMap; import org.json.JSONObject; -public class JSONFeatureModelFormat implements IFormat> { +public class JSONAnalysisFormat implements IFormat> { @Override public String getName() { diff --git a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java index b532f8e9..42b06e8a 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java @@ -24,7 +24,7 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; -import de.featjar.feature.model.io.json.JSONFeatureModelFormat; +import de.featjar.feature.model.io.json.JSONAnalysisFormat; import java.io.IOException; import java.util.HashMap; import java.util.LinkedHashMap; @@ -51,21 +51,18 @@ public void JSONTest() throws IOException { data.put("avgNumOfAsss", 4); AnalysisTree analsyisTree = AnalysisTree.hashMapToTree(data, "Analysis"); - JSONFeatureModelFormat jsonFormat = new JSONFeatureModelFormat(); + JSONAnalysisFormat jsonFormat = new JSONAnalysisFormat(); JSONObject firstJSONObject = new JSONObject(jsonFormat.serialize(analsyisTree).get()); String jsonString = firstJSONObject.toString(); JSONObject secondJSONJsonObject = new JSONObject(jsonString); HashMap jsonAsMap = (HashMap) secondJSONJsonObject.toMap(); - AnalysisTree analsyisTreeAfterConversion = AnalysisTree.hashMapListToTree(jsonAsMap, "Analysis"); + AnalysisTree analsyisTreeAfterConversion = AnalysisTree.hashMapListToTree(jsonAsMap); analsyisTree.sort(); analsyisTreeAfterConversion.sort(); - // TODO fix function and adjust test so it does not cheat assertTrue( - Trees.equals( - analsyisTree, analsyisTreeAfterConversion.getChild(0).get()), - "firstTree\n" + analsyisTree.print() + "\nsecond tree\n" - + analsyisTreeAfterConversion.getChild(0).get().print()); + Trees.equals(analsyisTree, analsyisTreeAfterConversion), + "firstTree\n" + analsyisTree.print() + "\nsecond tree\n" + analsyisTreeAfterConversion.print()); } } From 484502116e58a7e51b1f9f0ea489c1820b826b5d Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Thu, 9 Oct 2025 10:21:39 +0200 Subject: [PATCH 106/257] test: updatede test to check cloneNode. docs: added documentation to AnalysisTree --- .../feature/model/analysis/AnalysisTree.java | 33 ++++++++++++++++++- .../feature/model/AnalysisTreeTest.java | 3 ++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index 328007ca..45e4d524 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -21,6 +21,7 @@ package de.featjar.feature.model.analysis; import de.featjar.base.tree.structure.ATree; +import de.featjar.feature.model.io.json.JSONAnalysisFormat; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Arrays; @@ -29,6 +30,13 @@ import java.util.List; import java.util.Objects; +/** + * A tree of nodes with a given name and some data. + * + * @param type of value this node holds + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class AnalysisTree extends ATree> { String name; @@ -64,7 +72,6 @@ public AnalysisTree(AnalysisTree analysisTree) { @Override public AnalysisTree cloneNode() { - // TODO Auto-generated method stub return new AnalysisTree<>(this); } @@ -87,6 +94,14 @@ public int hashCodeNode() { return Objects.hash(this.getClass(), this.name, this.value); } + /** + * Static function to convert a nested HashMap having integers, floats, doubles and other recursively defined HashMaps as value + * to its AnalysisTree representation. + * + * @param hashMap data to convert + * @param name specifies the name of root + * @return returns a recursively built tree including its children + */ public static AnalysisTree hashMapToTree(HashMap hashMap, String name) { AnalysisTree root = new AnalysisTree<>(name, (Object) null); for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { @@ -106,6 +121,15 @@ public static AnalysisTree hashMapToTree(HashMap hashMap, Str return root; } + /** + * Static function to convert a nested hashmap being processed by {@link JSONAnalysisFormat} into its AnalysisTree representation. + * This function is not supposed to be called initially, otherwise a root node with + * name having the whole tree as single child is returned. + * + * @param hashMap data to convert + * @param name name of the root + * @return returns the recursively built tree including its children + */ public static AnalysisTree hashMapListToTree(HashMap hashMap, String name) { AnalysisTree root = new AnalysisTree<>(name, (Object) null); for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { @@ -128,6 +152,13 @@ public static AnalysisTree hashMapListToTree(HashMap hashMap, return root; } + /** + * Static function to convert a nested hashmap being processed by {@link JSONAnalysisFormat} into its AnalysisTree representation. + * This function is specially suited to process a hashmap having only a single key in its first layer. + * + * @param hashMap data to convert + * @return returns the recursively built tree including its children + */ public static AnalysisTree hashMapListToTree(HashMap hashMap) { if (hashMap.size() == 1) { String key = hashMap.keySet().iterator().next(); diff --git a/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java b/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java index f66103f1..308277e8 100644 --- a/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java +++ b/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; @@ -92,5 +93,7 @@ public void mapToTreeTest() { assertEquals(map2secondLevel.getChild(0).get().getValue(), (float) 21); assertEquals(map2secondLevel.getChild(1).get().getName(), "float2thirdLevel"); assertEquals(map2secondLevel.getChild(1).get().getValue(), (float) 22); + + assertTrue(Trees.equals(returnedTree, returnedTree.cloneTree())); } } From 747e6dc7833c8706df07f23cdbb4bb00a84101cf Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Thu, 9 Oct 2025 10:36:45 +0200 Subject: [PATCH 107/257] docs: Added documentation to the analysistreevisitor --- .../model/analysis/visitor/AnalysisTreeVisitor.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java index 677c4123..7d4942c9 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java @@ -30,6 +30,12 @@ import java.util.Iterator; import java.util.List; +/** + * Transforms a given AnalysisTree into a HashMap + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class AnalysisTreeVisitor implements ITreeVisitor, HashMap> { HashMap nodesMap = new HashMap(); @@ -46,7 +52,7 @@ public TraversalAction firstVisit(List> path) { for (Iterator> iterator = path.iterator(); iterator.hasNext(); ) { AnalysisTree currentAnalysisTree = iterator.next(); - if (iterator.hasNext()) { // || node.getName().equals(currentAnalysisTree.getName()) + if (iterator.hasNext()) { currentMap = (HashMap) currentMap.get(currentAnalysisTree.getName()); } } From 07855dd6674b589d0472184bb6ebf6c30e8eb3be Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 9 Oct 2025 10:46:56 +0200 Subject: [PATCH 108/257] feat: added command flag to allow overwriting the output file --- .../feature/model/cli/FormatConversion.java | 59 ++++++++++++------- .../model/cli/FormatConversionTest.java | 4 +- 2 files changed, 41 insertions(+), 22 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index bdf05a32..5d7efecb 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -33,7 +33,9 @@ import de.featjar.feature.model.io.xml.GraphVizFeatureModelFormat; import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; // --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output @@ -60,7 +62,9 @@ public class FormatConversion implements ICommand { public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) .setDescription("Path to output file. Accepted File Types: " + supportedInputFileExtensions); - + + public static final Option OVERWRITE = + Option.newFlag("overwrite").setDescription("Overwrite output file."); /** * {@return all options registered for the calling class} */ @@ -114,10 +118,6 @@ private enum FileInfo { */ @Override public int run(OptionList optionParser) { - - - System.out.println(FeatureModelFormats.getInstance().getExtensions()); - System.out.println(buildInfoLossMap()); if (!checkIfInputOutputIsPresent(optionParser)) { return 1; @@ -131,7 +131,8 @@ public int run(OptionList optionParser) { if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { return 1; } - + + infoLossMessage(inputFileExtension, outputFileExtension); // check if model was corrected extracted from input IFeatureModel model = inputParser(optionParser); @@ -141,13 +142,19 @@ public int run(OptionList optionParser) { } Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); - - return saveFile(outputPath, model, outputFileExtension); + + return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); } - public int testInfoLossMap() { + + + + + + // return 0 for no information loss. return 1 for information loss + public int infoLossMessage(String iExt, String oExt) { + String msg = "Info Loss:\n"; Map> infoLossMap = buildInfoLossMap(); - String iExt = "xml"; - String oExt = "txt"; + Map iSupports = infoLossMap.get(iExt); Map oSupports = infoLossMap.get(oExt); @@ -156,11 +163,15 @@ public int testInfoLossMap() { SupportLevel iSupportLevel = iSupports.get(fileInfo); SupportLevel oSupportLevel = oSupports.get(fileInfo); if (oSupportLevel.isLessThan(iSupportLevel)) { - System.out.println("Info Loss:"); - System.out.println(fileInfo); - System.out.println(" " + iExt + " support: " + iSupportLevel); - System.out.println(" " + oExt + " support: " + oSupportLevel); + msg += "\t Supports " + fileInfo + "\n \t\t" + iExt + ": " + iSupportLevel + "\n \t\t" + oExt + ": " + oSupportLevel + "\n"; } + + } + if(!msg.equals("Info Loss:\n")) { + FeatJAR.log().warning(msg); + return 1; + } else { + FeatJAR.log().message("No Information Loss from " + iExt + " to " + oExt + "."); } return 0; @@ -189,7 +200,7 @@ private Map> buildInfoLossMap () { supportMap.get(ext).put(FileInfo.featureGroups, SupportLevel.NONE); ext = "txt"; - supportMap.get(ext).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.FULL); + supportMap.get(ext).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); supportMap.get(ext).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); supportMap.get(ext).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); supportMap.get(ext).put(FileInfo.featureGroups, SupportLevel.NONE); @@ -243,7 +254,7 @@ private IFeatureModel inputParser(OptionList optionParser) { return model; } - public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension) { + public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { IFormat format; switch (outputFileExtension) { case "xml": @@ -253,7 +264,7 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten format = new GraphVizFeatureModelFormat(); break; case "txt": - format = new GenericTextFormat(); + format = new GenericTextFormat<>(); break; default: // this still catches errors if the switch case construct has not implemented all supported file types! @@ -261,10 +272,16 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten return 1; } try { - IO.save(model, outputPath, format); - if (model == null) { - throw new IOException("model has value null"); + if(Files.exists(outputPath)) { + if(overWriteOutputFile) { + FeatJAR.log().message("File already present at: " + outputPath + "\n\tContinuing to overwrite File."); + } else if(!overWriteOutputFile){ + FeatJAR.log().error("Saving outputModel in File unsuccessful: File already present at: " + outputPath + "\n\tTo overwrite present file add --overwrite"); + return 1; + } } + IO.save(model, outputPath, format); + } catch (IOException e) { FeatJAR.log().error(e); return 2; diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 3054bdb7..77b5693a 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -96,7 +96,9 @@ void invalid(){ // temp test for quick testing of the info loss map @Test void infoLossMapTest(){ - assertEquals(0, formatConversion.testInfoLossMap()); + assertEquals(1, formatConversion.infoLossMessage("xml", "txt")); + assertEquals(0, formatConversion.infoLossMessage("xml", "xml")); + } } From cc38f5f97846489c7ce161bf6bcc578d0c505307 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 9 Oct 2025 10:51:33 +0200 Subject: [PATCH 109/257] test: added a test for saving and loading with the JSONAnalysisFormat --- .../feature/model/analysis/AnalysisTree.java | 8 +-- .../model/io/json/JSONAnalysisFormat.java | 9 ++-- .../feature/model/io/JSONFExportTest.java | 54 ++++++++++++++++++- 3 files changed, 64 insertions(+), 7 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index 328007ca..123686db 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -39,14 +39,16 @@ public AnalysisTree(String name, T value) { this.value = value; } - protected AnalysisTree(AnalysisTree... children) { + public AnalysisTree(String name, AnalysisTree... children) { super(children.length); if (children.length > 0) super.setChildren(Arrays.asList(children)); + this.name = name; } - public AnalysisTree(List> children) { + public AnalysisTree(String name, List> children) { super(children.size()); super.setChildren(children); + this.name = name; } public String getName() { @@ -133,7 +135,7 @@ public static AnalysisTree hashMapListToTree(HashMap hashMap) String key = hashMap.keySet().iterator().next(); return hashMapListToTree((HashMap) hashMap.get(key), key); } else { - return new AnalysisTree<>(); + return new AnalysisTree<>(""); } } diff --git a/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java index fefccc2c..36b5647a 100644 --- a/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java +++ b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java @@ -29,6 +29,10 @@ import java.util.HashMap; import org.json.JSONObject; +/** + * An IFormat class that take an AnalysisTree as input and can serialize it into JSON String + * and from JSON String + */ public class JSONAnalysisFormat implements IFormat> { @Override @@ -55,14 +59,13 @@ public boolean supportsWrite() { public Result serialize(AnalysisTree analysisTree) { return Result.of(new JSONObject( Trees.traverse(analysisTree, new AnalysisTreeVisitor()).get()) - .toString(1)); // new XMLFeatureModelWriter().serialize(object); + .toString(1)); } @Override public Result> parse(AInputMapper inputMapper) { HashMap jsonMap = (HashMap) new JSONObject(inputMapper.get().text()).toMap(); - System.out.println("\n \n \n \n \n \n" + inputMapper.get().text()); - return Result.of(AnalysisTree.hashMapListToTree(jsonMap, "Analysis")); + return Result.of(AnalysisTree.hashMapListToTree(jsonMap)); } } diff --git a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java index 42b06e8a..ea65ea81 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java @@ -22,10 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; +import de.featjar.base.io.IO; import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.io.json.JSONAnalysisFormat; import java.io.IOException; +import java.nio.file.Paths; import java.util.HashMap; import java.util.LinkedHashMap; import org.json.JSONObject; @@ -35,8 +37,40 @@ public class JSONFExportTest { LinkedHashMap data = new LinkedHashMap(); + public AnalysisTree createDefaultTree() { + LinkedHashMap innerMap = new LinkedHashMap(); + innerMap.put("xo", 3.3); + innerMap.put("numOfLeafFeatures", (float) 12.4); + data.put("numOfTopFeatures", 3.3); + data.put("numOfLeafFeatures", (float) 12.4); + data.put("treeDepth", 3); + data.put("avgNumOfChildren", 3); + data.put("numInOrGroups", 7); + data.put("numInAltGroups", 5); + data.put("avgNumOfAtomsPerConstraints", innerMap); + data.put("numOfAtoms", 8); + data.put("avgNumOfAsss", 4); + AnalysisTree innereanalysisTree = new AnalysisTree<>( + "avgNumOfAtomsPerConstraints", + new AnalysisTree<>("xo", 3.3), + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); + + AnalysisTree analysisTree = new AnalysisTree<>( + "Analysis", + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), + new AnalysisTree<>("numOfTopFeatures", 3.3), + new AnalysisTree<>("treeDepth", 3), + new AnalysisTree<>("avgNumOfChildren", 3), + new AnalysisTree<>("numInOrGroups", 7), + new AnalysisTree<>("numInAltGroups", 5), + new AnalysisTree<>("numOfAtoms", 8), + new AnalysisTree<>("avgNumOfAsss", 4), + innereanalysisTree); + return analysisTree; + } + @Test - public void JSONTest() throws IOException { + public void JSONSerialize() throws IOException { LinkedHashMap innerMap = new LinkedHashMap(); innerMap.put("xo", 3.3); innerMap.put("numOfLeafFeatures", (float) 12.4); @@ -64,5 +98,23 @@ public void JSONTest() throws IOException { assertTrue( Trees.equals(analsyisTree, analsyisTreeAfterConversion), "firstTree\n" + analsyisTree.print() + "\nsecond tree\n" + analsyisTreeAfterConversion.print()); + AnalysisTree manualAnalysisTree = createDefaultTree(); + manualAnalysisTree.sort(); + assertTrue( + Trees.equals(manualAnalysisTree, analsyisTreeAfterConversion), + "firstTree\n" + manualAnalysisTree.print() + "\nsecond tree\n" + analsyisTreeAfterConversion.print()); + } + + @Test + public void JSONSaveLoadTest() throws IOException { + AnalysisTree analysisTree = createDefaultTree(); + IO.save(analysisTree, Paths.get("filename.json"), new JSONAnalysisFormat()); + AnalysisTree outputAnalysisTree = + IO.load(Paths.get("filename.json"), new JSONAnalysisFormat()).get(); + analysisTree.sort(); + outputAnalysisTree.sort(); + assertTrue( + Trees.equals(analysisTree, outputAnalysisTree), + "firstTree\n" + analysisTree.print() + "\nsecond tree\n" + outputAnalysisTree.print()); } } From 988b3dbbaf0b82e3ac341f24a71e4f80eefd290f Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 9 Oct 2025 11:03:32 +0200 Subject: [PATCH 110/257] feat: supported in- and output formats are now dynamically generated via extension point at runtime --- .../feature/model/cli/FormatConversion.java | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 5d7efecb..aca04bad 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -51,10 +51,14 @@ */ public class FormatConversion implements ICommand { - + private static final Map> supportedFileExtensions = buildSupportedFileExtensions(); + private static final List supportedInputFileExtensions = supportedFileExtensions.get("input"); + private static final List supportedOutputFileExtensions = supportedFileExtensions.get("output"); + /* private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "dot"); private static final List supportedOutputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "json", "dot"); + */ public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) @@ -146,7 +150,21 @@ public int run(OptionList optionParser) { return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); } - + private static Map> buildSupportedFileExtensions() { + List> supportedFileExtensions = FeatureModelFormats.getInstance().getExtensions(); + List supportedInputFileExtensions = new ArrayList<>(); + List supportedOutputFileExtensions = new ArrayList<>(); + + for (IFormat ext : supportedFileExtensions) { + if (ext.supportsParse()) {supportedInputFileExtensions.add(ext.getFileExtension());} + if (ext.supportsWrite()) {supportedOutputFileExtensions.add(ext.getFileExtension());} + } + + return Map.of( + "input", supportedInputFileExtensions, + "output", supportedOutputFileExtensions + ); + } From 1f6f33f08a791f2abfe121b2df33422de444a906 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 9 Oct 2025 11:03:56 +0200 Subject: [PATCH 111/257] style: gradlew style --- .../java/de/featjar/feature/model/analysis/AnalysisTree.java | 2 +- .../feature/model/analysis/visitor/AnalysisTreeVisitor.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index c2fd89d9..9a338048 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -159,7 +159,7 @@ public static AnalysisTree hashMapListToTree(HashMap hashMap, * This function is specially suited to process a hashmap having only a single key in its first layer. * * @param hashMap data to convert - * @return returns the recursively built tree including its children + * @return returns the recursively built tree including its children, or an empty tree with an empty name in case of an error */ public static AnalysisTree hashMapListToTree(HashMap hashMap) { if (hashMap.size() == 1) { diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java index 7d4942c9..aa121f2e 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java @@ -31,8 +31,8 @@ import java.util.List; /** - * Transforms a given AnalysisTree into a HashMap - * + * Transforms a given AnalysisTree into a HashMap + * * @author Mohammad Khair Almekkawi * @author Florian Beese */ From 29279ef591ff5bc9f1d702bc7626b8cff192d4d1 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 9 Oct 2025 14:10:33 +0200 Subject: [PATCH 112/257] test: added final test for checking content of created file --- .../feature/model/cli/FormatConversion.java | 99 ++++++++++++++----- .../model/cli/FormatConversionTest.java | 51 ++++++++-- 2 files changed, 116 insertions(+), 34 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index aca04bad..07b22174 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -38,8 +38,10 @@ import java.nio.file.Paths; import java.util.*; -// --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --scope all --pretty --output -// "c://home/desktop/model.xml" +// formatConversion --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --output "../../Desktop/model.xml" --overwrite + +// derzeit dynamisch implementiert von welchem dateityp man zu einem anderen dateityp konvertieren +// wir würden aber den daraus resultierenden information loss hard coden. Dynam // Extensionpoints um auf UVL zuzugreifen @@ -51,14 +53,21 @@ */ public class FormatConversion implements ICommand { - private static final Map> supportedFileExtensions = buildSupportedFileExtensions(); - private static final List supportedInputFileExtensions = supportedFileExtensions.get("input"); - private static final List supportedOutputFileExtensions = supportedFileExtensions.get("output"); - /* - private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "dot"); - private static final List supportedOutputFileExtensions = - Arrays.asList("csv", "xml", "yaml", "txt", "json", "dot"); - */ + /* + private static Map> supportedFileExtensions; + private static List supportedInputFileExtensions; + private static List supportedOutputFileExtensions; + */ + + private static Map> supportedFileExtensions = buildSupportedFileExtensions(); + private static List supportedInputFileExtensions = supportedFileExtensions.get("input"); + private static List supportedOutputFileExtensions = supportedFileExtensions.get("output"); + + +// private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "dot", "uvl"); +// private static final List supportedOutputFileExtensions = +// Arrays.asList("csv", "xml", "yaml", "txt", "json", "dot", "uvl"); + public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) @@ -122,6 +131,11 @@ private enum FileInfo { */ @Override public int run(OptionList optionParser) { + /* + supportedFileExtensions = buildSupportedFileExtensions(); + supportedInputFileExtensions = supportedFileExtensions.get("input"); + supportedOutputFileExtensions = supportedFileExtensions.get("output"); + */ if (!checkIfInputOutputIsPresent(optionParser)) { return 1; @@ -133,7 +147,7 @@ public int run(OptionList optionParser) { String outputFileExtension = IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { - return 1; + return 2; } infoLossMessage(inputFileExtension, outputFileExtension); @@ -146,12 +160,28 @@ public int run(OptionList optionParser) { } Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); + return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); } private static Map> buildSupportedFileExtensions() { - List> supportedFileExtensions = FeatureModelFormats.getInstance().getExtensions(); + + // todo can we do this cleaner? + try { + FeatJAR.initialize(); + } catch (Exception e) { + System.out.println("Already Initialized, Caught"); + } + + List> supportedFileExtensions = null; + + try { + supportedFileExtensions = FeatureModelFormats.getInstance().getExtensions(); + } catch (Exception e) { + FeatJAR.log().error(e); + } + List supportedInputFileExtensions = new ArrayList<>(); List supportedOutputFileExtensions = new ArrayList<>(); @@ -168,14 +198,20 @@ private static Map> buildSupportedFileExtensions() { - // return 0 for no information loss. return 1 for information loss + // return 0 for no information loss. return 1 for information loss, return 2 on error due to unsupported input or output file extensions public int infoLossMessage(String iExt, String oExt) { String msg = "Info Loss:\n"; Map> infoLossMap = buildInfoLossMap(); + System.out.print(supportedInputFileExtensions); + System.out.print(supportedOutputFileExtensions); - Map iSupports = infoLossMap.get(iExt); - Map oSupports = infoLossMap.get(oExt); + Map iSupports = infoLossMap.get(iExt);; + Map oSupports = infoLossMap.get(oExt);; + + if (iSupports == null || oSupports == null) { + return 2; + } for (FileInfo fileInfo : iSupports.keySet()) { SupportLevel iSupportLevel = iSupports.get(fileInfo); @@ -210,18 +246,24 @@ private Map> buildInfoLossMap () { } } - // fill with real values - String ext = "xml"; - supportMap.get(ext).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.FULL); - supportMap.get(ext).put(FileInfo.featureAttributesAndMetadata, SupportLevel.FULL); - supportMap.get(ext).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); - supportMap.get(ext).put(FileInfo.featureGroups, SupportLevel.NONE); - - ext = "txt"; - supportMap.get(ext).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); - supportMap.get(ext).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); - supportMap.get(ext).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); - supportMap.get(ext).put(FileInfo.featureGroups, SupportLevel.NONE); + // fill with real values maybe dynamically? + String extension = "xml"; + supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); + supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); + +// extension = "uvl"; +// supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); +// supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); +// supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); +// supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); + +// extension = "txt"; +// supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); +// supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); +// supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); +// supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); return supportMap; } @@ -284,6 +326,8 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten case "txt": format = new GenericTextFormat<>(); break; +// case "uvl": +// TODO default: // this still catches errors if the switch case construct has not implemented all supported file types! FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); @@ -304,6 +348,7 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten FeatJAR.log().error(e); return 2; } + FeatJAR.log().message("Output model saved at: " + outputPath); return 0; } diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 77b5693a..9310798f 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -26,35 +26,52 @@ import de.featjar.base.FeatJAR; +import de.featjar.base.data.Result; import de.featjar.base.data.identifier.AIdentifier; import de.featjar.base.data.identifier.IIdentifiable; +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.base.io.IO; import java.io.File; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.junit.jupiter.api.Test; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.cli.FormatConversion; +import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; /** * Tests for {@link AIdentifier} and {@link IIdentifiable}. * - * @author Knut & Kilian + * @author Knut, Kilian & Benjamin */ public class FormatConversionTest { + + + private FeatureModel generateMinimalModel() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + return featureModel; + } - FormatConversion formatConversion = new FormatConversion(); - + @Test void fileWritingTest() { - String pathToOutPutModel = "output_model.xml"; + String pathToOutPutModel = "../../Desktop/modelWritingTest.xml"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; int exit_code = FeatJAR.runTest( "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + System.out.println("exit_code: " + exit_code); assertEquals(0, exit_code); + System.out.println("Output File Exists: " + new File(pathToOutPutModel).exists()); + assertTrue(new File(pathToOutPutModel).exists()); Path pathToBeDeleted = Paths.get(pathToOutPutModel); assertDoesNotThrow(() -> { @@ -70,7 +87,8 @@ void invalidOutput(){ int exit_code = FeatJAR.runTest( "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); - assertEquals(1, exit_code); + System.out.println(exit_code); + assertEquals(2, exit_code); } @Test void invalidInput(){ @@ -93,12 +111,31 @@ void invalid(){ assertEquals(1, exit_code); } - // temp test for quick testing of the info loss map @Test void infoLossMapTest(){ - assertEquals(1, formatConversion.infoLossMessage("xml", "txt")); + FormatConversion formatConversion = new FormatConversion(); + + assertEquals(2, formatConversion.infoLossMessage("xml", "pdf")); + assertEquals(1, formatConversion.infoLossMessage("xml", "dot")); assertEquals(0, formatConversion.infoLossMessage("xml", "xml")); } + + @Test + void testWriteAndOverwrite() throws IOException { + + Path outputPath = Paths.get("../../Desktop/COPYTHIS.xml"); + FeatureModel model = generateMinimalModel(); + + // let program write model to XML file + new FormatConversion().saveFile(outputPath, model, "xml", true); + + // roundtrip: rebuild model from XML file + FeatureModel retrievedModel = (FeatureModel) IO.load(outputPath, new XMLFeatureModelFormat()).get(); + + assertEquals(model, retrievedModel); + + Files.deleteIfExists(outputPath); + } } From f9091c3187351e226f5b8c1b6587f2b32508ceda Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 9 Oct 2025 14:12:50 +0200 Subject: [PATCH 113/257] style: applied styleguide --- .../feature/model/cli/FormatConversion.java | 192 ++++++++++-------- .../model/cli/FormatConversionTest.java | 104 +++++----- .../model/cli/PrintStatisticsTest.java | 1 - 3 files changed, 149 insertions(+), 148 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 07b22174..9201079d 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -35,17 +35,16 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.*; -// formatConversion --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --output "../../Desktop/model.xml" --overwrite +// formatConversion --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --output +// "../../Desktop/model.xml" --overwrite // derzeit dynamisch implementiert von welchem dateityp man zu einem anderen dateityp konvertieren // wir würden aber den daraus resultierenden information loss hard coden. Dynam // Extensionpoints um auf UVL zuzugreifen - /** * Prints statistics about a provided Feature Model. * @@ -53,21 +52,20 @@ */ public class FormatConversion implements ICommand { - /* - private static Map> supportedFileExtensions; - private static List supportedInputFileExtensions; - private static List supportedOutputFileExtensions; - */ - - private static Map> supportedFileExtensions = buildSupportedFileExtensions(); - private static List supportedInputFileExtensions = supportedFileExtensions.get("input"); - private static List supportedOutputFileExtensions = supportedFileExtensions.get("output"); - - -// private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", "dot", "uvl"); -// private static final List supportedOutputFileExtensions = -// Arrays.asList("csv", "xml", "yaml", "txt", "json", "dot", "uvl"); - + /* + private static Map> supportedFileExtensions; + private static List supportedInputFileExtensions; + private static List supportedOutputFileExtensions; + */ + + private static Map> supportedFileExtensions = buildSupportedFileExtensions(); + private static List supportedInputFileExtensions = supportedFileExtensions.get("input"); + private static List supportedOutputFileExtensions = supportedFileExtensions.get("output"); + + // private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", + // "dot", "uvl"); + // private static final List supportedOutputFileExtensions = + // Arrays.asList("csv", "xml", "yaml", "txt", "json", "dot", "uvl"); public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) @@ -75,7 +73,7 @@ public class FormatConversion implements ICommand { public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) .setDescription("Path to output file. Accepted File Types: " + supportedInputFileExtensions); - + public static final Option OVERWRITE = Option.newFlag("overwrite").setDescription("Overwrite output file."); /** @@ -92,9 +90,14 @@ private enum SupportLevel { FULL(2); public final int rank; - SupportLevel(int rank) {this.rank = rank;} - boolean isLessThan(SupportLevel other) {return this.rank < other.rank; } + SupportLevel(int rank) { + this.rank = rank; + } + + boolean isLessThan(SupportLevel other) { + return this.rank < other.rank; + } } // for info loss map @@ -103,7 +106,8 @@ private enum FileInfo { hierarchicalFeatureStructure("Hierarchical feature structure"), featureAttributesAndMetadata("Feature attributes and metadata"), mandatoryAndOptionalFeatures("Mandatory and optional features"), - featureGroups("Feature groups (AND, OR, XOR)", + featureGroups( + "Feature groups (AND, OR, XOR)", "AND groups are equivalent to cardinality groups ranging from 1 to 1, and OR from 1 to n."); public final String name; @@ -120,7 +124,9 @@ private enum FileInfo { } @Override - public String toString() { return description.isEmpty() ? name : name + ": " + description; } + public String toString() { + return description.isEmpty() ? name : name + ": " + description; + } } /** @@ -131,12 +137,12 @@ private enum FileInfo { */ @Override public int run(OptionList optionParser) { - /* - supportedFileExtensions = buildSupportedFileExtensions(); - supportedInputFileExtensions = supportedFileExtensions.get("input"); - supportedOutputFileExtensions = supportedFileExtensions.get("output"); - */ - + /* + supportedFileExtensions = buildSupportedFileExtensions(); + supportedInputFileExtensions = supportedFileExtensions.get("input"); + supportedOutputFileExtensions = supportedFileExtensions.get("output"); + */ + if (!checkIfInputOutputIsPresent(optionParser)) { return 1; } @@ -149,7 +155,7 @@ public int run(OptionList optionParser) { if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { return 2; } - + infoLossMessage(inputFileExtension, outputFileExtension); // check if model was corrected extracted from input @@ -161,77 +167,80 @@ public int run(OptionList optionParser) { Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); - return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); } - + private static Map> buildSupportedFileExtensions() { - - // todo can we do this cleaner? - try { - FeatJAR.initialize(); - } catch (Exception e) { - System.out.println("Already Initialized, Caught"); - } - - List> supportedFileExtensions = null; - - try { - supportedFileExtensions = FeatureModelFormats.getInstance().getExtensions(); - } catch (Exception e) { - FeatJAR.log().error(e); - } - + + // todo can we do this cleaner? + try { + FeatJAR.initialize(); + } catch (Exception e) { + System.out.println("Already Initialized, Caught"); + } + + List> supportedFileExtensions = null; + + try { + supportedFileExtensions = FeatureModelFormats.getInstance().getExtensions(); + } catch (Exception e) { + FeatJAR.log().error(e); + } + List supportedInputFileExtensions = new ArrayList<>(); List supportedOutputFileExtensions = new ArrayList<>(); for (IFormat ext : supportedFileExtensions) { - if (ext.supportsParse()) {supportedInputFileExtensions.add(ext.getFileExtension());} - if (ext.supportsWrite()) {supportedOutputFileExtensions.add(ext.getFileExtension());} + if (ext.supportsParse()) { + supportedInputFileExtensions.add(ext.getFileExtension()); + } + if (ext.supportsWrite()) { + supportedOutputFileExtensions.add(ext.getFileExtension()); + } } return Map.of( "input", supportedInputFileExtensions, - "output", supportedOutputFileExtensions - ); + "output", supportedOutputFileExtensions); } - - - - // return 0 for no information loss. return 1 for information loss, return 2 on error due to unsupported input or output file extensions + + // return 0 for no information loss. return 1 for information loss, return 2 on error due to unsupported input or + // output file extensions public int infoLossMessage(String iExt, String oExt) { - String msg = "Info Loss:\n"; + String msg = "Info Loss:\n"; Map> infoLossMap = buildInfoLossMap(); System.out.print(supportedInputFileExtensions); System.out.print(supportedOutputFileExtensions); - Map iSupports = infoLossMap.get(iExt);; - Map oSupports = infoLossMap.get(oExt);; - + Map iSupports = infoLossMap.get(iExt); + ; + Map oSupports = infoLossMap.get(oExt); + ; + if (iSupports == null || oSupports == null) { - return 2; + return 2; } for (FileInfo fileInfo : iSupports.keySet()) { SupportLevel iSupportLevel = iSupports.get(fileInfo); SupportLevel oSupportLevel = oSupports.get(fileInfo); if (oSupportLevel.isLessThan(iSupportLevel)) { - msg += "\t Supports " + fileInfo + "\n \t\t" + iExt + ": " + iSupportLevel + "\n \t\t" + oExt + ": " + oSupportLevel + "\n"; + msg += "\t Supports " + fileInfo + "\n \t\t" + iExt + ": " + iSupportLevel + "\n \t\t" + oExt + ": " + + oSupportLevel + "\n"; } - } - if(!msg.equals("Info Loss:\n")) { + if (!msg.equals("Info Loss:\n")) { FeatJAR.log().warning(msg); return 1; } else { - FeatJAR.log().message("No Information Loss from " + iExt + " to " + oExt + "."); + FeatJAR.log().message("No Information Loss from " + iExt + " to " + oExt + "."); } return 0; } - private Map> buildInfoLossMap () { + private Map> buildInfoLossMap() { Map> supportMap = new HashMap<>(); // set to eliminate duplicates @@ -239,10 +248,12 @@ private Map> buildInfoLossMap () { supportedFileExtensions.addAll(supportedOutputFileExtensions); // default values - for (String fileExtension: supportedFileExtensions) { + for (String fileExtension : supportedFileExtensions) { supportMap.put(fileExtension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature for (FileInfo fileInfo : FileInfo.values()) { - supportMap.get(fileExtension).put(fileInfo, SupportLevel.NONE); // by default: all features are unsupported + supportMap + .get(fileExtension) + .put(fileInfo, SupportLevel.NONE); // by default: all features are unsupported } } @@ -253,17 +264,17 @@ private Map> buildInfoLossMap () { supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); -// extension = "uvl"; -// supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); -// supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); -// supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); -// supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); - -// extension = "txt"; -// supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); -// supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); -// supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); -// supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); + // extension = "uvl"; + // supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); + // supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); + // supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); + // supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); + + // extension = "txt"; + // supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); + // supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); + // supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); + // supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); return supportMap; } @@ -313,7 +324,7 @@ private IFeatureModel inputParser(OptionList optionParser) { } return model; } - + public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { IFormat format; switch (outputFileExtension) { @@ -326,21 +337,24 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten case "txt": format = new GenericTextFormat<>(); break; -// case "uvl": -// TODO + // case "uvl": + // TODO default: // this still catches errors if the switch case construct has not implemented all supported file types! FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); return 1; } try { - if(Files.exists(outputPath)) { - if(overWriteOutputFile) { - FeatJAR.log().message("File already present at: " + outputPath + "\n\tContinuing to overwrite File."); - } else if(!overWriteOutputFile){ - FeatJAR.log().error("Saving outputModel in File unsuccessful: File already present at: " + outputPath + "\n\tTo overwrite present file add --overwrite"); - return 1; - } + if (Files.exists(outputPath)) { + if (overWriteOutputFile) { + FeatJAR.log() + .message("File already present at: " + outputPath + "\n\tContinuing to overwrite File."); + } else if (!overWriteOutputFile) { + FeatJAR.log() + .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath + + "\n\tTo overwrite present file add --overwrite"); + return 1; + } } IO.save(model, outputPath, format); diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 9310798f..b9d77e19 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -20,18 +20,17 @@ */ package de.featjar.feature.model.cli; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - import de.featjar.base.FeatJAR; -import de.featjar.base.data.Result; import de.featjar.base.data.identifier.AIdentifier; import de.featjar.base.data.identifier.IIdentifiable; import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.io.IO; - +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; import java.io.File; import java.io.IOException; import java.nio.file.Files; @@ -39,35 +38,26 @@ import java.nio.file.Paths; import org.junit.jupiter.api.Test; -import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.IFeatureModel; -import de.featjar.feature.model.cli.FormatConversion; -import de.featjar.feature.model.io.FeatureModelFormats; -import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; - /** * Tests for {@link AIdentifier} and {@link IIdentifiable}. * * @author Knut, Kilian & Benjamin */ public class FormatConversionTest { - - + private FeatureModel generateMinimalModel() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); return featureModel; } - @Test void fileWritingTest() { - - String pathToOutPutModel = "../../Desktop/modelWritingTest.xml"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; - int exit_code = FeatJAR.runTest( - "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + String pathToOutPutModel = "../../Desktop/modelWritingTest.xml"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + + int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); System.out.println("exit_code: " + exit_code); assertEquals(0, exit_code); System.out.println("Output File Exists: " + new File(pathToOutPutModel).exists()); @@ -75,67 +65,65 @@ void fileWritingTest() { assertTrue(new File(pathToOutPutModel).exists()); Path pathToBeDeleted = Paths.get(pathToOutPutModel); assertDoesNotThrow(() -> { - Files.deleteIfExists(pathToBeDeleted); - } ); + Files.deleteIfExists(pathToBeDeleted); + }); } - + @Test - void invalidOutput(){ - - String pathToOutPutModel = "output_model.pdf"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + void invalidOutput() { + + String pathToOutPutModel = "output_model.pdf"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; - int exit_code = FeatJAR.runTest( - "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); System.out.println(exit_code); assertEquals(2, exit_code); } + @Test - void invalidInput(){ - - String pathToOutPutModel = "output_model.xml"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; - - int exit_code = FeatJAR.runTest( - "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + void invalidInput() { + + String pathToOutPutModel = "output_model.xml"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; + + int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); assertEquals(1, exit_code); } + @Test - void invalid(){ - - String pathToOutPutModel = "output_model.xml"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; - - int exit_code = FeatJAR.runTest( - "formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + void invalid() { + + String pathToOutPutModel = "output_model.xml"; + String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; + + int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); assertEquals(1, exit_code); } @Test - void infoLossMapTest(){ - FormatConversion formatConversion = new FormatConversion(); + void infoLossMapTest() { + FormatConversion formatConversion = new FormatConversion(); - assertEquals(2, formatConversion.infoLossMessage("xml", "pdf")); + assertEquals(2, formatConversion.infoLossMessage("xml", "pdf")); assertEquals(1, formatConversion.infoLossMessage("xml", "dot")); assertEquals(0, formatConversion.infoLossMessage("xml", "xml")); - } - + @Test void testWriteAndOverwrite() throws IOException { - - Path outputPath = Paths.get("../../Desktop/COPYTHIS.xml"); - FeatureModel model = generateMinimalModel(); - - // let program write model to XML file - new FormatConversion().saveFile(outputPath, model, "xml", true); - - // roundtrip: rebuild model from XML file - FeatureModel retrievedModel = (FeatureModel) IO.load(outputPath, new XMLFeatureModelFormat()).get(); - + + Path outputPath = Paths.get("../../Desktop/COPYTHIS.xml"); + FeatureModel model = generateMinimalModel(); + + // let program write model to XML file + new FormatConversion().saveFile(outputPath, model, "xml", true); + + // roundtrip: rebuild model from XML file + FeatureModel retrievedModel = + (FeatureModel) IO.load(outputPath, new XMLFeatureModelFormat()).get(); + assertEquals(model, retrievedModel); - + Files.deleteIfExists(outputPath); } - } diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index aeab5eb6..45e987ed 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -108,7 +108,6 @@ void scopeAll() { } @Test - void scopeTreeRelated() { String content = From 77a6950636a0a8803167c99229c655cf6ecf0771 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 9 Oct 2025 14:16:34 +0200 Subject: [PATCH 114/257] doc: added doc stubs --- .../feature/model/cli/FormatConversion.java | 46 +++++++++++++++++-- .../model/cli/FormatConversionTest.java | 15 ++++++ 2 files changed, 56 insertions(+), 5 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 9201079d..1e347b87 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -83,7 +83,9 @@ public final List> getOptions() { return Option.getAllOptions(getClass()); } - // for info loss map + /** + * for info loss map; indicates whether a feature is supported fully, partially, or not at all. + */ private enum SupportLevel { NONE(0), PARTIAL(1), @@ -100,8 +102,12 @@ boolean isLessThan(SupportLevel other) { } } - // for info loss map - // saving name as well as a description in case we need to explain it to the user later + + + /** + * for info loss map + * saving name as well as a description in case we need to explain it to the user later + */ private enum FileInfo { hierarchicalFeatureStructure("Hierarchical feature structure"), featureAttributesAndMetadata("Feature attributes and metadata"), @@ -170,6 +176,10 @@ public int run(OptionList optionParser) { return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); } + /** + * + * @return + */ private static Map> buildSupportedFileExtensions() { // todo can we do this cleaner? @@ -204,8 +214,12 @@ private static Map> buildSupportedFileExtensions() { "output", supportedOutputFileExtensions); } - // return 0 for no information loss. return 1 for information loss, return 2 on error due to unsupported input or - // output file extensions + /** + * + * @param iExt + * @param oExt + * @return 0 for no information loss. 1 for information loss, 2 on error due to unsupported input or + */ public int infoLossMessage(String iExt, String oExt) { String msg = "Info Loss:\n"; Map> infoLossMap = buildInfoLossMap(); @@ -240,6 +254,10 @@ public int infoLossMessage(String iExt, String oExt) { return 0; } + /** + * + * @return + */ private Map> buildInfoLossMap() { Map> supportMap = new HashMap<>(); @@ -302,6 +320,11 @@ private boolean checkIfFileExtensionsValid(String inputFileExtension, String out return true; } + /** + * + * @param optionParser + * @return + */ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { if (!optionParser.getResult(INPUT_OPTION).isPresent()) { FeatJAR.log().error("No input path provided."); @@ -313,6 +336,11 @@ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { return true; } + /** + * + * @param optionParser + * @return + */ private IFeatureModel inputParser(OptionList optionParser) { Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); IFeatureModel model = null; @@ -325,6 +353,14 @@ private IFeatureModel inputParser(OptionList optionParser) { return model; } + /** + * + * @param outputPath + * @param model + * @param outputFileExtension + * @param overWriteOutputFile + * @return + */ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { IFormat format; switch (outputFileExtension) { diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index b9d77e19..31f2172f 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -51,6 +51,9 @@ private FeatureModel generateMinimalModel() { return featureModel; } + /** + * + */ @Test void fileWritingTest() { @@ -69,6 +72,9 @@ void fileWritingTest() { }); } + /** + * + */ @Test void invalidOutput() { @@ -80,6 +86,9 @@ void invalidOutput() { assertEquals(2, exit_code); } + /** + * + */ @Test void invalidInput() { @@ -90,6 +99,9 @@ void invalidInput() { assertEquals(1, exit_code); } + /** + * + */ @Test void invalid() { @@ -100,6 +112,9 @@ void invalid() { assertEquals(1, exit_code); } + /** + * + */ @Test void infoLossMapTest() { FormatConversion formatConversion = new FormatConversion(); From 82aaba7a2ee6a7c9ac98fa9f8eabd227b7df1937 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 9 Oct 2025 14:39:29 +0200 Subject: [PATCH 115/257] doc: added test comments --- .../model/cli/FormatConversionTest.java | 35 +++++++------------ 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 31f2172f..16ca323f 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -52,20 +52,17 @@ private FeatureModel generateMinimalModel() { } /** - * + * Tests whether an XML file can be loaded and written elsewhere (no content check) */ @Test void fileWritingTest() { - String pathToOutPutModel = "../../Desktop/modelWritingTest.xml"; String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); - System.out.println("exit_code: " + exit_code); assertEquals(0, exit_code); - System.out.println("Output File Exists: " + new File(pathToOutPutModel).exists()); - assertTrue(new File(pathToOutPutModel).exists()); + Path pathToBeDeleted = Paths.get(pathToOutPutModel); assertDoesNotThrow(() -> { Files.deleteIfExists(pathToBeDeleted); @@ -73,7 +70,7 @@ void fileWritingTest() { } /** - * + * Attempts to write model to an incompatible file format (.pdf) and checks whether it's rejected correctly. */ @Test void invalidOutput() { @@ -82,12 +79,11 @@ void invalidOutput() { String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); - System.out.println(exit_code); assertEquals(2, exit_code); } /** - * + * Attempts to read model from an incompatible file format (.pdf) and checks whether it's rejected correctly. */ @Test void invalidInput() { @@ -100,30 +96,23 @@ void invalidInput() { } /** - * - */ - @Test - void invalid() { - - String pathToOutPutModel = "output_model.xml"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; - - int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); - assertEquals(1, exit_code); - } - - /** - * + * Tests whether information loss warnings are given when appropriate. */ @Test void infoLossMapTest() { FormatConversion formatConversion = new FormatConversion(); + // output extension should not be found in information loss map assertEquals(2, formatConversion.infoLossMessage("xml", "pdf")); + // this input / output file extension combination should trigger an info loss warning assertEquals(1, formatConversion.infoLossMessage("xml", "dot")); + // this input / output file extension combination should NOT trigger an info loss warning assertEquals(0, formatConversion.infoLossMessage("xml", "xml")); } + /** + * Tests whether the converter can do an XML -> XML round trip with a basic feature model. + */ @Test void testWriteAndOverwrite() throws IOException { @@ -133,7 +122,7 @@ void testWriteAndOverwrite() throws IOException { // let program write model to XML file new FormatConversion().saveFile(outputPath, model, "xml", true); - // roundtrip: rebuild model from XML file + // round trip: rebuild model from XML file FeatureModel retrievedModel = (FeatureModel) IO.load(outputPath, new XMLFeatureModelFormat()).get(); From 26acaecf1b12898e954efdad06d56460b012c428 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 9 Oct 2025 15:27:55 +0200 Subject: [PATCH 116/257] feat: added first implementation for the yamlAnalysisFormater. test: add Test for the created Formatter that need further development. --- build.gradle | 1 + .../feature/model/analysis/AnalysisTree.java | 37 +++++- .../analysis/visitor/AnalysisTreeVisitor.java | 5 +- .../model/io/json/JSONAnalysisFormat.java | 71 ----------- .../model/io/yaml/YAMLAnalysisFormat.java | 69 ++++++++++ ...ONFExportTest.java => JSONExportTest.java} | 14 +-- .../feature/model/io/YAMLExportTest.java | 118 ++++++++++++++++++ 7 files changed, 228 insertions(+), 87 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java rename src/test/java/de/featjar/feature/model/io/{JSONFExportTest.java => JSONExportTest.java} (88%) create mode 100644 src/test/java/de/featjar/feature/model/io/YAMLExportTest.java diff --git a/build.gradle b/build.gradle index 8a6b8f7b..6f4e9863 100644 --- a/build.gradle +++ b/build.gradle @@ -7,6 +7,7 @@ dependencies { api 'de.featjar:formula' api testFixtures('de.featjar:formula') implementation 'org.json:json:20240303' + implementation 'org.yaml:snakeyaml:2.5' } license { diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index 9a338048..4541e4bd 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -39,8 +39,8 @@ */ public class AnalysisTree extends ATree> { - String name; - T value; + private String name; + private T value; public AnalysisTree(String name, T value) { this.name = name; @@ -169,6 +169,39 @@ public static AnalysisTree hashMapListToTree(HashMap hashMap) return new AnalysisTree<>(""); } } + + public static AnalysisTree hashMapListYamlToTree(HashMap hashMap, String name) { + AnalysisTree root = new AnalysisTree<>(name, (Object) null); + for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { + String currentKey = iterator.next(); + if (hashMap.get(currentKey) instanceof HashMap) { + root.addChild(hashMapListYamlToTree((HashMap) hashMap.get(currentKey), currentKey)); + } else if (hashMap.get(currentKey) instanceof ArrayList) { + ArrayList currentElement = (ArrayList) hashMap.get(currentKey); + if (currentElement.get(1).equals("class java.lang.Double")) { + double currentDeccimal = (double) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal)); + } else if (currentElement.get(1).equals("class java.lang.Integer")) { + root.addChild(new AnalysisTree<>(currentKey, (int) currentElement.get(2))); + } else if (currentElement.get(1).equals("class java.lang.Float")) { + double currentDouble = (double) currentElement.get(2); + float currentDeccimal = (float) currentDouble; + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal)); + } + } + } + return root; + } + + + public static AnalysisTree hashMapListYamlToTree(HashMap hashMap) { + if (hashMap.size() == 1) { + String key = hashMap.keySet().iterator().next(); + return hashMapListYamlToTree((HashMap) hashMap.get(key), key); + } else { + return new AnalysisTree<>(""); + } + } @Override public String toString() { diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java index aa121f2e..1c4c7921 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java @@ -32,6 +32,9 @@ /** * Transforms a given AnalysisTree into a HashMap + * Where the resulted HashMap is a HashMap that have other HashMap or a list that contains no further + * HashMaps but exactly three elements, with the first element being a name, the second a value, the third the type of the value. + * Furthermore, the types of the values that are allowed are only Integer, Double, or Float. * * @author Mohammad Khair Almekkawi * @author Florian Beese @@ -61,7 +64,7 @@ public TraversalAction firstVisit(List> path) { currentMap.put( node.getName(), new ArrayList( - Arrays.asList(node.getName(), node.getValue().getClass(), node.getValue()))); + Arrays.asList(node.getName(), node.getValue().getClass().toString(), node.getValue()))); } else { currentMap.put(node.getName(), new HashMap()); } diff --git a/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java index 36b5647a..e69de29b 100644 --- a/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java +++ b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java @@ -1,71 +0,0 @@ -/* - * Copyright (C) 2025 FeatJAR-Development-Team - * - * This file is part of FeatJAR-feature-model. - * - * feature-model is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3.0 of the License, - * or (at your option) any later version. - * - * feature-model is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with feature-model. If not, see . - * - * See for further information. - */ -package de.featjar.feature.model.io.json; - -import de.featjar.base.data.Result; -import de.featjar.base.io.format.IFormat; -import de.featjar.base.io.input.AInputMapper; -import de.featjar.base.tree.Trees; -import de.featjar.feature.model.analysis.AnalysisTree; -import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; -import java.util.HashMap; -import org.json.JSONObject; - -/** - * An IFormat class that take an AnalysisTree as input and can serialize it into JSON String - * and from JSON String - */ -public class JSONAnalysisFormat implements IFormat> { - - @Override - public String getName() { - return "JSON"; - } - - @Override - public String getFileExtension() { - return "json"; - } - - @Override - public boolean supportsParse() { - return true; - } - - @Override - public boolean supportsWrite() { - return true; - } - - @Override - public Result serialize(AnalysisTree analysisTree) { - return Result.of(new JSONObject( - Trees.traverse(analysisTree, new AnalysisTreeVisitor()).get()) - .toString(1)); - } - - @Override - public Result> parse(AInputMapper inputMapper) { - HashMap jsonMap = - (HashMap) new JSONObject(inputMapper.get().text()).toMap(); - return Result.of(AnalysisTree.hashMapListToTree(jsonMap)); - } -} diff --git a/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java b/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java new file mode 100644 index 00000000..1e90320f --- /dev/null +++ b/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.io.yaml; + +import de.featjar.base.data.Result; +import de.featjar.base.io.format.IFormat; +import de.featjar.base.io.input.AInputMapper; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; + +import java.util.HashMap; +import java.util.LinkedHashMap; + +import org.yaml.snakeyaml.Yaml; + +public class YAMLAnalysisFormat implements IFormat> { + + @Override + public String getName() { + return "YAML"; + } + + @Override + public String getFileExtension() { + return "yaml"; + } + + @Override + public boolean supportsParse() { + return true; + } + + @Override + public boolean supportsWrite() { + return true; + } + + @Override + public Result serialize(AnalysisTree analysisTree) { + Yaml yaml = new Yaml(); + return Result.of(yaml.dump(Trees.traverse(analysisTree, new AnalysisTreeVisitor()).get())); + } + + @Override + public Result> parse(AInputMapper inputMapper) { + Yaml yaml = new Yaml(); + HashMap yamlHashMap = (HashMap) yaml.load(inputMapper.get().text()); + return Result.of(AnalysisTree.hashMapListYamlToTree(yamlHashMap)); + } +} diff --git a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java similarity index 88% rename from src/test/java/de/featjar/feature/model/io/JSONFExportTest.java rename to src/test/java/de/featjar/feature/model/io/JSONExportTest.java index ea65ea81..fcec4c07 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONFExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java @@ -33,23 +33,11 @@ import org.json.JSONObject; import org.junit.jupiter.api.Test; -public class JSONFExportTest { +public class JSONExportTest { LinkedHashMap data = new LinkedHashMap(); public AnalysisTree createDefaultTree() { - LinkedHashMap innerMap = new LinkedHashMap(); - innerMap.put("xo", 3.3); - innerMap.put("numOfLeafFeatures", (float) 12.4); - data.put("numOfTopFeatures", 3.3); - data.put("numOfLeafFeatures", (float) 12.4); - data.put("treeDepth", 3); - data.put("avgNumOfChildren", 3); - data.put("numInOrGroups", 7); - data.put("numInAltGroups", 5); - data.put("avgNumOfAtomsPerConstraints", innerMap); - data.put("numOfAtoms", 8); - data.put("avgNumOfAsss", 4); AnalysisTree innereanalysisTree = new AnalysisTree<>( "avgNumOfAtomsPerConstraints", new AnalysisTree<>("xo", 3.3), diff --git a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java new file mode 100644 index 00000000..c5a5ac67 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java @@ -0,0 +1,118 @@ +package de.featjar.feature.model.io; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Paths; +import java.util.HashMap; +import java.util.LinkedHashMap; + +import org.json.JSONObject; +import org.junit.jupiter.api.Test; +import org.yaml.snakeyaml.Yaml; +import org.yaml.snakeyaml.constructor.SafeConstructor; + +import de.featjar.base.data.Result; +import de.featjar.base.io.IO; +import de.featjar.base.io.input.AInputMapper; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.io.json.JSONAnalysisFormat; +import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; + +public class YAMLExportTest { + + LinkedHashMap data = new LinkedHashMap(); + + public AnalysisTree createDefaultTree() { + AnalysisTree innereanalysisTree = new AnalysisTree<>( + "avgNumOfAtomsPerConstraints", + new AnalysisTree<>("xo", 3.3), + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); + + AnalysisTree analysisTree = new AnalysisTree<>( + "Analysis", + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), + new AnalysisTree<>("numOfTopFeatures", 3.3), + new AnalysisTree<>("treeDepth", 3), + new AnalysisTree<>("avgNumOfChildren", 3), + new AnalysisTree<>("numInOrGroups", 7), + new AnalysisTree<>("numInAltGroups", 5), + new AnalysisTree<>("numOfAtoms", 8), + new AnalysisTree<>("avgNumOfAsss", 4), + innereanalysisTree); + return analysisTree; + } + + @Test + public void YAMLTest() throws IOException{ + AnalysisTree analysisTree = createDefaultTree(); + IO.save(analysisTree, Paths.get("filename.yaml"), new YAMLAnalysisFormat()); + AnalysisTree outputAnalysisTree = + IO.load(Paths.get("filename.yaml"), new YAMLAnalysisFormat()).get(); + analysisTree.sort(); + outputAnalysisTree.sort(); + assertTrue( + Trees.equals(analysisTree, outputAnalysisTree), + "firstTree\n" + analysisTree.print() + "\nsecond tree\n" + outputAnalysisTree.print()); + } + + @Test + public void JSONSerialize() throws IOException { + LinkedHashMap innerMap = new LinkedHashMap(); + innerMap.put("xo", 3.3); + innerMap.put("numOfLeafFeatures", (float) 12.4); + data.put("numOfTopFeatures", 3.3); + data.put("numOfLeafFeatures", (float) 12.4); + data.put("treeDepth", 3); + data.put("avgNumOfChildren", 3); + data.put("numInOrGroups", 7); + data.put("numInAltGroups", 5); + data.put("avgNumOfAtomsPerConstraints", innerMap); + data.put("numOfAtoms", 8); + data.put("avgNumOfAsss", 4); + + AnalysisTree analsyisTree = createDefaultTree(); + YAMLAnalysisFormat yamlFormat = new YAMLAnalysisFormat(); + Yaml yaml = new Yaml(); + System.out.println("yamlFormat.serialize(analsyisTree).get() : \n" + yamlFormat.serialize(analsyisTree).get()); + String yamlFromserializerDumpString = yaml.dump(yamlFormat.serialize(analsyisTree).get()); + System.out.println(yamlFromserializerDumpString); + + AInputMapper inputMapper = AInputMapper.of(Paths.get("filename.yaml"), IO.DEFAULT_CHARSET); + + Yaml yaml1 = new Yaml(); + HashMap yamlHashMap = (HashMap) yaml1.load(inputMapper.get().text()); + inputMapper = AInputMapper.of(Paths.get("filename.yaml"), IO.DEFAULT_CHARSET); + Yaml yaml2 = new Yaml(); + + String yamlLoadFromDump = yaml2.load(yamlFromserializerDumpString); + String yamlFromFileInputMapper = inputMapper.get().text(); + System.out.println("yamlLoadFromDump: \n" + yamlLoadFromDump.getClass()); + System.out.println("yamlFromFileInputMapper: \n" + yamlFromFileInputMapper.getClass()); + System.out.println("yamlFromFileInputMapper: \n" + yamlFromFileInputMapper); + System.out.println("yamlLoadFromDump: \n" + yamlLoadFromDump); + System.out.println("yamlFromserializerDumpString: \n" + yamlFromserializerDumpString); + + System.out.println("yamlFromFileInputMapper 1: \n" + yamlFromFileInputMapper.toString()); + System.out.println("yamlLoadFromDump 1: \n" + yamlLoadFromDump.toString()); + System.out.println("yamlFromserializerDumpString 1: \n" + yamlFromserializerDumpString); + + yaml2.load(yamlFromserializerDumpString); + AnalysisTree analsyisTreeAfterConversion = Result.of(AnalysisTree.hashMapListYamlToTree(yamlHashMap)).get(); + + + analsyisTree.sort(); + analsyisTreeAfterConversion.sort(); + assertTrue( + Trees.equals(analsyisTree, analsyisTreeAfterConversion), + "firstTree\n" + analsyisTree.print() + "\nsecond tree\n" + analsyisTreeAfterConversion.print()); + AnalysisTree manualAnalysisTree = createDefaultTree(); + manualAnalysisTree.sort(); + assertTrue( + Trees.equals(manualAnalysisTree, analsyisTreeAfterConversion), + "firstTree\n" + manualAnalysisTree.print() + "\nsecond tree\n" + analsyisTreeAfterConversion.print()); + } + +} From 655e5af820a6cf3288b8660e1799aa2273c9ef66 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 9 Oct 2025 15:16:53 +0200 Subject: [PATCH 117/257] refactor: improved quality of tests --- .../feature/model/cli/FormatConversion.java | 88 +++++-------------- .../model/cli/FormatConversionTest.java | 70 +++++++++------ 2 files changed, 63 insertions(+), 95 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 1e347b87..39fc7d15 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -27,7 +27,6 @@ import de.featjar.base.data.Result; import de.featjar.base.io.IO; import de.featjar.base.io.format.IFormat; -import de.featjar.base.io.text.GenericTextFormat; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.io.FeatureModelFormats; import de.featjar.feature.model.io.xml.GraphVizFeatureModelFormat; @@ -37,14 +36,6 @@ import java.nio.file.Path; import java.util.*; -// formatConversion --input "../formula/src/testFixtures/resources/Automotive02_V1/model.xml" --output -// "../../Desktop/model.xml" --overwrite - -// derzeit dynamisch implementiert von welchem dateityp man zu einem anderen dateityp konvertieren -// wir würden aber den daraus resultierenden information loss hard coden. Dynam - -// Extensionpoints um auf UVL zuzugreifen - /** * Prints statistics about a provided Feature Model. * @@ -52,21 +43,10 @@ */ public class FormatConversion implements ICommand { - /* - private static Map> supportedFileExtensions; - private static List supportedInputFileExtensions; - private static List supportedOutputFileExtensions; - */ - private static Map> supportedFileExtensions = buildSupportedFileExtensions(); private static List supportedInputFileExtensions = supportedFileExtensions.get("input"); private static List supportedOutputFileExtensions = supportedFileExtensions.get("output"); - // private static final List supportedInputFileExtensions = Arrays.asList("csv", "xml", "yaml", "txt", - // "dot", "uvl"); - // private static final List supportedOutputFileExtensions = - // Arrays.asList("csv", "xml", "yaml", "txt", "json", "dot", "uvl"); - public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) .setValidator(Option.PathValidator); @@ -76,6 +56,7 @@ public class FormatConversion implements ICommand { public static final Option OVERWRITE = Option.newFlag("overwrite").setDescription("Overwrite output file."); + /** * {@return all options registered for the calling class} */ @@ -102,8 +83,6 @@ boolean isLessThan(SupportLevel other) { } } - - /** * for info loss map * saving name as well as a description in case we need to explain it to the user later @@ -143,11 +122,6 @@ public String toString() { */ @Override public int run(OptionList optionParser) { - /* - supportedFileExtensions = buildSupportedFileExtensions(); - supportedInputFileExtensions = supportedFileExtensions.get("input"); - supportedOutputFileExtensions = supportedFileExtensions.get("output"); - */ if (!checkIfInputOutputIsPresent(optionParser)) { return 1; @@ -182,11 +156,8 @@ public int run(OptionList optionParser) { */ private static Map> buildSupportedFileExtensions() { - // todo can we do this cleaner? - try { + if (!FeatJAR.isInitialized()) { FeatJAR.initialize(); - } catch (Exception e) { - System.out.println("Already Initialized, Caught"); } List> supportedFileExtensions = null; @@ -221,16 +192,12 @@ private static Map> buildSupportedFileExtensions() { * @return 0 for no information loss. 1 for information loss, 2 on error due to unsupported input or */ public int infoLossMessage(String iExt, String oExt) { + String msg = "Info Loss:\n"; Map> infoLossMap = buildInfoLossMap(); - System.out.print(supportedInputFileExtensions); - System.out.print(supportedOutputFileExtensions); - - Map iSupports = infoLossMap.get(iExt); - ; + Map iSupports = infoLossMap.get(iExt); // xml Map oSupports = infoLossMap.get(oExt); - ; if (iSupports == null || oSupports == null) { return 2; @@ -239,6 +206,7 @@ public int infoLossMessage(String iExt, String oExt) { for (FileInfo fileInfo : iSupports.keySet()) { SupportLevel iSupportLevel = iSupports.get(fileInfo); SupportLevel oSupportLevel = oSupports.get(fileInfo); + if (oSupportLevel.isLessThan(iSupportLevel)) { msg += "\t Supports " + fileInfo + "\n \t\t" + iExt + ": " + iSupportLevel + "\n \t\t" + oExt + ": " + oSupportLevel + "\n"; @@ -259,40 +227,29 @@ public int infoLossMessage(String iExt, String oExt) { * @return */ private Map> buildInfoLossMap() { - Map> supportMap = new HashMap<>(); - // set to eliminate duplicates - Set supportedFileExtensions = new LinkedHashSet<>(supportedInputFileExtensions); - supportedFileExtensions.addAll(supportedOutputFileExtensions); - - // default values - for (String fileExtension : supportedFileExtensions) { - supportMap.put(fileExtension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - for (FileInfo fileInfo : FileInfo.values()) { - supportMap - .get(fileExtension) - .put(fileInfo, SupportLevel.NONE); // by default: all features are unsupported - } - } + Map> supportMap = new HashMap<>(); - // fill with real values maybe dynamically? String extension = "xml"; + supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.FULL); supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.FULL); supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); - // extension = "uvl"; - // supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); - // supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); - // supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); - // supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); + extension = "uvl"; + supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature + supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); + supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); - // extension = "txt"; - // supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); - // supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); - // supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); - // supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); + extension = "dot"; + supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature + supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); + supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); return supportMap; } @@ -353,7 +310,7 @@ private IFeatureModel inputParser(OptionList optionParser) { return model; } - /** + /** TODO: * * @param outputPath * @param model @@ -370,11 +327,6 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten case "dot": format = new GraphVizFeatureModelFormat(); break; - case "txt": - format = new GenericTextFormat<>(); - break; - // case "uvl": - // TODO default: // this still catches errors if the switch case construct has not implemented all supported file types! FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 16ca323f..a90c6628 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -20,7 +20,6 @@ */ package de.featjar.feature.model.cli; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -45,61 +44,76 @@ */ public class FormatConversionTest { - private FeatureModel generateMinimalModel() { + private FeatureModel generateModel() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); return featureModel; } + private String inputPath = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + private String outputPath; + + /** - * Tests whether an XML file can be loaded and written elsewhere (no content check) + * Attempts to write model to an incompatible file format (.pdf) and checks whether it's rejected correctly. + * @throws IOException + * */ @Test - void fileWritingTest() { - String pathToOutPutModel = "../../Desktop/modelWritingTest.xml"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + void fileWritingTest() throws IOException { + + outputPath = "model_fileWritingTest.xml"; + + Files.deleteIfExists(Paths.get(outputPath)); - int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); + int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); assertEquals(0, exit_code); - assertTrue(new File(pathToOutPutModel).exists()); + assertTrue(new File(outputPath).exists()); - Path pathToBeDeleted = Paths.get(pathToOutPutModel); - assertDoesNotThrow(() -> { - Files.deleteIfExists(pathToBeDeleted); - }); + Files.deleteIfExists(Paths.get(outputPath)); } /** - * Attempts to write model to an incompatible file format (.pdf) and checks whether it's rejected correctly. + * @throws IOException + * */ @Test - void invalidOutput() { + void invalidInput() throws IOException { - String pathToOutPutModel = "output_model.pdf"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + inputPath = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; + outputPath = "model_invalidInput.xml"; - int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); - assertEquals(2, exit_code); + Files.deleteIfExists(Paths.get(outputPath)); + + int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); + assertEquals(1, exit_code); + + Files.deleteIfExists(Paths.get(outputPath)); } /** - * Attempts to read model from an incompatible file format (.pdf) and checks whether it's rejected correctly. + * @throws IOException + * */ @Test - void invalidInput() { + void invalidOutput() throws IOException { - String pathToOutPutModel = "output_model.xml"; - String pathToInputModel = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; + outputPath = "model_invalidOutput.pdf"; - int exit_code = FeatJAR.runTest("formatConversion", "--input", pathToInputModel, "--output", pathToOutPutModel); - assertEquals(1, exit_code); + Files.deleteIfExists(Paths.get(outputPath)); + + int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); + assertEquals(2, exit_code); + + Files.deleteIfExists(Paths.get(outputPath)); } /** - * Tests whether information loss warnings are given when appropriate. + * */ @Test void infoLossMapTest() { + FormatConversion formatConversion = new FormatConversion(); // output extension should not be found in information loss map @@ -116,8 +130,10 @@ void infoLossMapTest() { @Test void testWriteAndOverwrite() throws IOException { - Path outputPath = Paths.get("../../Desktop/COPYTHIS.xml"); - FeatureModel model = generateMinimalModel(); + Path outputPath = Paths.get("model_testWriteAndOverwrite.xml"); + FeatureModel model = generateModel(); + + Files.deleteIfExists(outputPath); // let program write model to XML file new FormatConversion().saveFile(outputPath, model, "xml", true); From 5053f60c16b8e477c5410cb46a50501fb39654ce Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 9 Oct 2025 15:37:41 +0200 Subject: [PATCH 118/257] doc: added test documentation --- .../feature/model/cli/FormatConversionTest.java | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index a90c6628..d3ca1a5f 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -44,6 +44,9 @@ */ public class FormatConversionTest { + /** + * {@return example feature model for testing purposes} + */ private FeatureModel generateModel() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); @@ -56,8 +59,6 @@ private FeatureModel generateModel() { /** * Attempts to write model to an incompatible file format (.pdf) and checks whether it's rejected correctly. - * @throws IOException - * */ @Test void fileWritingTest() throws IOException { @@ -74,8 +75,7 @@ void fileWritingTest() throws IOException { } /** - * @throws IOException - * + * Attempts to read model from an incompatible file format (.pdf) and checks whether it's rejected correctly. */ @Test void invalidInput() throws IOException { @@ -92,8 +92,7 @@ void invalidInput() throws IOException { } /** - * @throws IOException - * + * Attempts to write model to an incompatible file format (.pdf) and checks whether it's rejected correctly. */ @Test void invalidOutput() throws IOException { @@ -109,7 +108,7 @@ void invalidOutput() throws IOException { } /** - * + * Tests whether information loss warnings are given when appropriate. */ @Test void infoLossMapTest() { From 8408eac20b22ed98063dfd3e043846f77f9280ba Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Thu, 9 Oct 2025 15:53:10 +0200 Subject: [PATCH 119/257] fix:restored deleted file. test:added a simple yaml serialization loadsave test --- .../feature/model/analysis/AnalysisTree.java | 9 +- .../analysis/visitor/AnalysisTreeVisitor.java | 4 +- .../model/io/json/JSONAnalysisFormat.java | 71 ++++++++++++++ .../model/io/yaml/YAMLAnalysisFormat.java | 15 ++- .../feature/model/AnalysisTreeTest.java | 2 + .../feature/model/io/YAMLExportTest.java | 97 +++++++++---------- 6 files changed, 132 insertions(+), 66 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index 4541e4bd..088fee24 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -169,7 +169,7 @@ public static AnalysisTree hashMapListToTree(HashMap hashMap) return new AnalysisTree<>(""); } } - + public static AnalysisTree hashMapListYamlToTree(HashMap hashMap, String name) { AnalysisTree root = new AnalysisTree<>(name, (Object) null); for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { @@ -184,16 +184,15 @@ public static AnalysisTree hashMapListYamlToTree(HashMap hash } else if (currentElement.get(1).equals("class java.lang.Integer")) { root.addChild(new AnalysisTree<>(currentKey, (int) currentElement.get(2))); } else if (currentElement.get(1).equals("class java.lang.Float")) { - double currentDouble = (double) currentElement.get(2); - float currentDeccimal = (float) currentDouble; + double currentDouble = (double) currentElement.get(2); + float currentDeccimal = (float) currentDouble; root.addChild(new AnalysisTree<>(currentKey, currentDeccimal)); } } } return root; } - - + public static AnalysisTree hashMapListYamlToTree(HashMap hashMap) { if (hashMap.size() == 1) { String key = hashMap.keySet().iterator().next(); diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java index 1c4c7921..581d4a04 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java @@ -63,8 +63,8 @@ public TraversalAction firstVisit(List> path) { if (node.getChildrenCount() == 0) { currentMap.put( node.getName(), - new ArrayList( - Arrays.asList(node.getName(), node.getValue().getClass().toString(), node.getValue()))); + new ArrayList(Arrays.asList( + node.getName(), node.getValue().getClass().toString(), node.getValue()))); } else { currentMap.put(node.getName(), new HashMap()); } diff --git a/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java index e69de29b..36b5647a 100644 --- a/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java +++ b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java @@ -0,0 +1,71 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.io.json; + +import de.featjar.base.data.Result; +import de.featjar.base.io.format.IFormat; +import de.featjar.base.io.input.AInputMapper; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; +import java.util.HashMap; +import org.json.JSONObject; + +/** + * An IFormat class that take an AnalysisTree as input and can serialize it into JSON String + * and from JSON String + */ +public class JSONAnalysisFormat implements IFormat> { + + @Override + public String getName() { + return "JSON"; + } + + @Override + public String getFileExtension() { + return "json"; + } + + @Override + public boolean supportsParse() { + return true; + } + + @Override + public boolean supportsWrite() { + return true; + } + + @Override + public Result serialize(AnalysisTree analysisTree) { + return Result.of(new JSONObject( + Trees.traverse(analysisTree, new AnalysisTreeVisitor()).get()) + .toString(1)); + } + + @Override + public Result> parse(AInputMapper inputMapper) { + HashMap jsonMap = + (HashMap) new JSONObject(inputMapper.get().text()).toMap(); + return Result.of(AnalysisTree.hashMapListToTree(jsonMap)); + } +} diff --git a/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java b/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java index 1e90320f..18864392 100644 --- a/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java +++ b/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java @@ -26,10 +26,7 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; - import java.util.HashMap; -import java.util.LinkedHashMap; - import org.yaml.snakeyaml.Yaml; public class YAMLAnalysisFormat implements IFormat> { @@ -56,14 +53,16 @@ public boolean supportsWrite() { @Override public Result serialize(AnalysisTree analysisTree) { - Yaml yaml = new Yaml(); - return Result.of(yaml.dump(Trees.traverse(analysisTree, new AnalysisTreeVisitor()).get())); + Yaml yaml = new Yaml(); + return Result.of(yaml.dump( + Trees.traverse(analysisTree, new AnalysisTreeVisitor()).get())); } @Override public Result> parse(AInputMapper inputMapper) { - Yaml yaml = new Yaml(); - HashMap yamlHashMap = (HashMap) yaml.load(inputMapper.get().text()); - return Result.of(AnalysisTree.hashMapListYamlToTree(yamlHashMap)); + Yaml yaml = new Yaml(); + HashMap yamlHashMap = + (HashMap) yaml.load(inputMapper.get().text()); + return Result.of(AnalysisTree.hashMapListYamlToTree(yamlHashMap)); } } diff --git a/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java b/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java index 308277e8..d93c1fb2 100644 --- a/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java +++ b/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java @@ -21,6 +21,7 @@ package de.featjar.feature.model; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertTrue; import de.featjar.base.tree.Trees; @@ -37,6 +38,7 @@ public void mapToTreeTest() { assertEquals(returnedTree.getName(), "empty"); assertEquals(returnedTree.getChildrenCount(), 0); + assertNull(returnedTree.getValue()); emptyMap.put("intfirstLevel", 42); emptyMap.put("floatfirstLevel", (float) 42); diff --git a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java index c5a5ac67..e87fcfc2 100644 --- a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java @@ -1,31 +1,44 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.io; -import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; +import de.featjar.base.io.IO; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; import java.io.IOException; import java.nio.file.Paths; import java.util.HashMap; import java.util.LinkedHashMap; - -import org.json.JSONObject; +import java.util.Map; import org.junit.jupiter.api.Test; import org.yaml.snakeyaml.Yaml; -import org.yaml.snakeyaml.constructor.SafeConstructor; - -import de.featjar.base.data.Result; -import de.featjar.base.io.IO; -import de.featjar.base.io.input.AInputMapper; -import de.featjar.base.tree.Trees; -import de.featjar.feature.model.analysis.AnalysisTree; -import de.featjar.feature.model.io.json.JSONAnalysisFormat; -import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; public class YAMLExportTest { - - LinkedHashMap data = new LinkedHashMap(); - - public AnalysisTree createDefaultTree() { + + LinkedHashMap data = new LinkedHashMap(); + + public AnalysisTree createDefaultTree() { AnalysisTree innereanalysisTree = new AnalysisTree<>( "avgNumOfAtomsPerConstraints", new AnalysisTree<>("xo", 3.3), @@ -44,10 +57,10 @@ public AnalysisTree createDefaultTree() { innereanalysisTree); return analysisTree; } - - @Test - public void YAMLTest() throws IOException{ - AnalysisTree analysisTree = createDefaultTree(); + + @Test + public void YAMLTest() throws IOException { + AnalysisTree analysisTree = createDefaultTree(); IO.save(analysisTree, Paths.get("filename.yaml"), new YAMLAnalysisFormat()); AnalysisTree outputAnalysisTree = IO.load(Paths.get("filename.yaml"), new YAMLAnalysisFormat()).get(); @@ -56,10 +69,10 @@ public void YAMLTest() throws IOException{ assertTrue( Trees.equals(analysisTree, outputAnalysisTree), "firstTree\n" + analysisTree.print() + "\nsecond tree\n" + outputAnalysisTree.print()); - } - - @Test - public void JSONSerialize() throws IOException { + } + + @Test + public void JSONSerialize() throws IOException { LinkedHashMap innerMap = new LinkedHashMap(); innerMap.put("xo", 3.3); innerMap.put("numOfLeafFeatures", (float) 12.4); @@ -76,32 +89,15 @@ public void JSONSerialize() throws IOException { AnalysisTree analsyisTree = createDefaultTree(); YAMLAnalysisFormat yamlFormat = new YAMLAnalysisFormat(); Yaml yaml = new Yaml(); - System.out.println("yamlFormat.serialize(analsyisTree).get() : \n" + yamlFormat.serialize(analsyisTree).get()); - String yamlFromserializerDumpString = yaml.dump(yamlFormat.serialize(analsyisTree).get()); - System.out.println(yamlFromserializerDumpString); - - AInputMapper inputMapper = AInputMapper.of(Paths.get("filename.yaml"), IO.DEFAULT_CHARSET); - - Yaml yaml1 = new Yaml(); - HashMap yamlHashMap = (HashMap) yaml1.load(inputMapper.get().text()); - inputMapper = AInputMapper.of(Paths.get("filename.yaml"), IO.DEFAULT_CHARSET); - Yaml yaml2 = new Yaml(); - - String yamlLoadFromDump = yaml2.load(yamlFromserializerDumpString); - String yamlFromFileInputMapper = inputMapper.get().text(); - System.out.println("yamlLoadFromDump: \n" + yamlLoadFromDump.getClass()); - System.out.println("yamlFromFileInputMapper: \n" + yamlFromFileInputMapper.getClass()); - System.out.println("yamlFromFileInputMapper: \n" + yamlFromFileInputMapper); - System.out.println("yamlLoadFromDump: \n" + yamlLoadFromDump); - System.out.println("yamlFromserializerDumpString: \n" + yamlFromserializerDumpString); - - System.out.println("yamlFromFileInputMapper 1: \n" + yamlFromFileInputMapper.toString()); - System.out.println("yamlLoadFromDump 1: \n" + yamlLoadFromDump.toString()); - System.out.println("yamlFromserializerDumpString 1: \n" + yamlFromserializerDumpString); - - yaml2.load(yamlFromserializerDumpString); - AnalysisTree analsyisTreeAfterConversion = Result.of(AnalysisTree.hashMapListYamlToTree(yamlHashMap)).get(); - + String output = yamlFormat.serialize(analsyisTree).get(); + + Yaml yaml2 = new Yaml(); + Object loadedObject = yaml2.load(output); + + Map map = (Map) loadedObject; + HashMap loadedHashMap = (HashMap) map; + + AnalysisTree analsyisTreeAfterConversion = AnalysisTree.hashMapListYamlToTree(loadedHashMap); analsyisTree.sort(); analsyisTreeAfterConversion.sort(); @@ -114,5 +110,4 @@ public void JSONSerialize() throws IOException { Trees.equals(manualAnalysisTree, analsyisTreeAfterConversion), "firstTree\n" + manualAnalysisTree.print() + "\nsecond tree\n" + analsyisTreeAfterConversion.print()); } - } From c8c55d12c9c1bf373d415bf95ea34dbba3b5622d Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 9 Oct 2025 16:24:30 +0200 Subject: [PATCH 120/257] test: prototype logger evaluation --- .../feature/model/cli/FormatConversion.java | 31 +++++----- .../model/cli/FormatConversionTest.java | 57 ++++++++++++++----- 2 files changed, 56 insertions(+), 32 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 39fc7d15..05f0805b 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -58,14 +58,14 @@ public class FormatConversion implements ICommand { Option.newFlag("overwrite").setDescription("Overwrite output file."); /** - * {@return all options registered for the calling class} + * @return all options registered for the calling class. */ public final List> getOptions() { return Option.getAllOptions(getClass()); } /** - * for info loss map; indicates whether a feature is supported fully, partially, or not at all. + * For info loss map; indicates whether a feature is supported fully, partially, or not at all. */ private enum SupportLevel { NONE(0), @@ -84,8 +84,8 @@ boolean isLessThan(SupportLevel other) { } /** - * for info loss map - * saving name as well as a description in case we need to explain it to the user later + * For info loss map. + * Saving name as well as a description in case we need to explain it to the user later. */ private enum FileInfo { hierarchicalFeatureStructure("Hierarchical feature structure"), @@ -115,14 +115,14 @@ public String toString() { } /** + * main function for handling format conversion + * @param OptionParser supplied by command line execution. * - * @param optionParser option parser supplied by command line execution - * - * @return 0 on success, 1 if in- or output paths are invalid, 2 on IOException, 3 if no model could be parsed from input file + * @return 0 if success, 1 if input/output paths are invalid, 2 if IOException, 3 if no model could be parsed from input file. */ @Override public int run(OptionList optionParser) { - + if (!checkIfInputOutputIsPresent(optionParser)) { return 1; } @@ -136,6 +136,7 @@ public int run(OptionList optionParser) { return 2; } + // informing user about information loss during conversion between file formats infoLossMessage(inputFileExtension, outputFileExtension); // check if model was corrected extracted from input @@ -150,9 +151,8 @@ public int run(OptionList optionParser) { return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); } - /** - * - * @return + /** Iterates over an extension point to compile lists of the supported file extensions. + * @return One list that contains all supported input file extensions (under the key: "input"), and one list that contains all supported output file extensions (key: "output". */ private static Map> buildSupportedFileExtensions() { @@ -191,7 +191,7 @@ private static Map> buildSupportedFileExtensions() { * @param oExt * @return 0 for no information loss. 1 for information loss, 2 on error due to unsupported input or */ - public int infoLossMessage(String iExt, String oExt) { + private void infoLossMessage(String iExt, String oExt) { String msg = "Info Loss:\n"; Map> infoLossMap = buildInfoLossMap(); @@ -200,7 +200,7 @@ public int infoLossMessage(String iExt, String oExt) { Map oSupports = infoLossMap.get(oExt); if (iSupports == null || oSupports == null) { - return 2; + return; } for (FileInfo fileInfo : iSupports.keySet()) { @@ -214,12 +214,9 @@ public int infoLossMessage(String iExt, String oExt) { } if (!msg.equals("Info Loss:\n")) { FeatJAR.log().warning(msg); - return 1; } else { FeatJAR.log().message("No Information Loss from " + iExt + " to " + oExt + "."); } - - return 0; } /** @@ -310,7 +307,7 @@ private IFeatureModel inputParser(OptionList optionParser) { return model; } - /** TODO: + /** * * @param outputPath * @param model diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index a90c6628..cbc3d23a 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -24,22 +24,24 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import de.featjar.base.FeatJAR; -import de.featjar.base.data.identifier.AIdentifier; -import de.featjar.base.data.identifier.IIdentifiable; import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.io.IO; +import de.featjar.base.log.ConfigurableLog.Configuration; +import de.featjar.base.log.Log.Verbosity; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; + +import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; +import java.io.PrintStream; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import org.junit.jupiter.api.Test; /** - * Tests for {@link AIdentifier} and {@link IIdentifiable}. - * + * @throws IOException * @author Knut, Kilian & Benjamin */ public class FormatConversionTest { @@ -56,7 +58,6 @@ private FeatureModel generateModel() { /** * Attempts to write model to an incompatible file format (.pdf) and checks whether it's rejected correctly. - * @throws IOException * */ @Test @@ -74,7 +75,7 @@ void fileWritingTest() throws IOException { } /** - * @throws IOException + * * */ @Test @@ -92,7 +93,7 @@ void invalidInput() throws IOException { } /** - * @throws IOException + * * */ @Test @@ -110,22 +111,48 @@ void invalidOutput() throws IOException { /** * + * checks if an info loss message is produced */ @Test - void infoLossMapTest() { + void infoLossMapTestTriggers() throws IOException { + + inputPath = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + outputPath = "model_invalidInput.dot"; - FormatConversion formatConversion = new FormatConversion(); + Files.deleteIfExists(Paths.get(outputPath)); + + FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); + + + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(out); + de.featjar.base.FeatJAR.Configuration config = FeatJAR.configure(); + config + .logConfig + .logToStream(stream, "", Verbosity.MESSAGE, Verbosity.WARNING); + FeatJAR.initialize(config); + - // output extension should not be found in information loss map - assertEquals(2, formatConversion.infoLossMessage("xml", "pdf")); - // this input / output file extension combination should trigger an info loss warning - assertEquals(1, formatConversion.infoLossMessage("xml", "dot")); - // this input / output file extension combination should NOT trigger an info loss warning - assertEquals(0, formatConversion.infoLossMessage("xml", "xml")); + FormatConversion formatConversion = new FormatConversion(); + + byte[] byteArray = out.toByteArray(); + String string = new String(byteArray); + // System.out.println(string); // to check what was written to logger + String expected_output = "Info Loss:\n" + + " Supports Feature attributes and metadata\n" + + " xml: FULL\n" + + " dot: NONE\n" + + " Supports Mandatory and optional features\n" + + " xml: FULL\n" + + " dot: NONE\n"; + assertTrue(string.contains(expected_output)); + + // assertEquals(expected_output, string); } /** * Tests whether the converter can do an XML -> XML round trip with a basic feature model. + * */ @Test void testWriteAndOverwrite() throws IOException { From f00124cf642d55c57b87d1673479c54b8e43d601 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 9 Oct 2025 16:27:07 +0200 Subject: [PATCH 121/257] style: small import cleanup --- .../java/de/featjar/feature/model/cli/FormatConversionTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 3a7ffce0..8f9fcbcf 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -26,7 +26,6 @@ import de.featjar.base.FeatJAR; import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.io.IO; -import de.featjar.base.log.ConfigurableLog.Configuration; import de.featjar.base.log.Log.Verbosity; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; @@ -41,7 +40,6 @@ import org.junit.jupiter.api.Test; /** - * @throws IOException * @author Knut, Kilian & Benjamin */ public class FormatConversionTest { From 164b5be8fecd8304414a737bd5c7188d3004918b Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 9 Oct 2025 16:42:16 +0200 Subject: [PATCH 122/257] fix: fixed file not being deleted post-test --- .../featjar/feature/model/cli/FormatConversionTest.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 8f9fcbcf..3174d38c 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -122,9 +122,6 @@ void infoLossMapTestTriggers() throws IOException { Files.deleteIfExists(Paths.get(outputPath)); - FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); - - ByteArrayOutputStream out = new ByteArrayOutputStream(); PrintStream stream = new PrintStream(out); de.featjar.base.FeatJAR.Configuration config = FeatJAR.configure(); @@ -133,8 +130,7 @@ void infoLossMapTestTriggers() throws IOException { .logToStream(stream, "", Verbosity.MESSAGE, Verbosity.WARNING); FeatJAR.initialize(config); - - FormatConversion formatConversion = new FormatConversion(); + FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); byte[] byteArray = out.toByteArray(); String string = new String(byteArray); @@ -149,6 +145,8 @@ void infoLossMapTestTriggers() throws IOException { assertTrue(string.contains(expected_output)); // assertEquals(expected_output, string); + + Files.deleteIfExists(Paths.get(outputPath)); } /** From a899036abfbab9ff44dceed25c7e08c2e82f68d5 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 10:15:44 +0200 Subject: [PATCH 123/257] fix: infoLossMapTest fixed --- .../feature/model/cli/FormatConversion.java | 4 +- .../model/cli/FormatConversionTest.java | 50 +++++++++---------- 2 files changed, 25 insertions(+), 29 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 05f0805b..a15304ce 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -122,7 +122,7 @@ public String toString() { */ @Override public int run(OptionList optionParser) { - + if (!checkIfInputOutputIsPresent(optionParser)) { return 1; } @@ -307,7 +307,7 @@ private IFeatureModel inputParser(OptionList optionParser) { return model; } - /** + /** * * @param outputPath * @param model diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 3174d38c..a0caa417 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -29,7 +29,6 @@ import de.featjar.base.log.Log.Verbosity; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; - import java.io.ByteArrayOutputStream; import java.io.File; import java.io.IOException; @@ -40,13 +39,11 @@ import org.junit.jupiter.api.Test; /** + * @throws IOException * @author Knut, Kilian & Benjamin */ public class FormatConversionTest { - /** - * {@return example feature model for testing purposes} - */ private FeatureModel generateModel() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); @@ -56,7 +53,6 @@ private FeatureModel generateModel() { private String inputPath = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; private String outputPath; - /** * Attempts to write model to an incompatible file format (.pdf) and checks whether it's rejected correctly. * @@ -78,7 +74,6 @@ void fileWritingTest() throws IOException { /** * * - * Attempts to read model from an incompatible file format (.pdf) and checks whether it's rejected correctly. */ @Test void invalidInput() throws IOException { @@ -95,7 +90,8 @@ void invalidInput() throws IOException { } /** - * Attempts to write model to an incompatible file format (.pdf) and checks whether it's rejected correctly. + * + * */ @Test void invalidOutput() throws IOException { @@ -122,29 +118,29 @@ void infoLossMapTestTriggers() throws IOException { Files.deleteIfExists(Paths.get(outputPath)); - ByteArrayOutputStream out = new ByteArrayOutputStream(); - PrintStream stream = new PrintStream(out); - de.featjar.base.FeatJAR.Configuration config = FeatJAR.configure(); - config - .logConfig - .logToStream(stream, "", Verbosity.MESSAGE, Verbosity.WARNING); - FeatJAR.initialize(config); - FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); + // Using FeatJAR logger + ByteArrayOutputStream out = new ByteArrayOutputStream(); + PrintStream stream = new PrintStream(out); + + de.featjar.base.FeatJAR.Configuration config = FeatJAR.configure(); + config.logConfig.logToStream(stream, "", Verbosity.MESSAGE, Verbosity.WARNING); + FeatJAR.initialize(config); + FeatJAR.runInternally("formatConversion", "--input", inputPath, "--output", outputPath); + byte[] byteArray = out.toByteArray(); - String string = new String(byteArray); - // System.out.println(string); // to check what was written to logger - String expected_output = "Info Loss:\n" - + " Supports Feature attributes and metadata\n" - + " xml: FULL\n" - + " dot: NONE\n" - + " Supports Mandatory and optional features\n" - + " xml: FULL\n" - + " dot: NONE\n"; - assertTrue(string.contains(expected_output)); - - // assertEquals(expected_output, string); + String string = new String(byteArray); + + String expected_output = "Info Loss:\n" + + " Supports Feature attributes and metadata\n" + + " xml: FULL\n" + + " dot: NONE\n" + + " Supports Mandatory and optional features\n" + + " xml: FULL\n" + + " dot: NONE\n" + + ""; + assertTrue(string.startsWith(expected_output)); Files.deleteIfExists(Paths.get(outputPath)); } From c436e48ddaa2a81b0e5210d571aa23d920282da5 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 12:02:28 +0200 Subject: [PATCH 124/257] refactor: removed anti patterns --- .../feature/model/cli/FormatConversion.java | 91 ++++++++++++------- .../model/cli/FormatConversionTest.java | 16 ++-- 2 files changed, 68 insertions(+), 39 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index a15304ce..0df45d04 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -88,13 +88,15 @@ boolean isLessThan(SupportLevel other) { * Saving name as well as a description in case we need to explain it to the user later. */ private enum FileInfo { - hierarchicalFeatureStructure("Hierarchical feature structure"), - featureAttributesAndMetadata("Feature attributes and metadata"), - mandatoryAndOptionalFeatures("Mandatory and optional features"), - featureGroups( - "Feature groups (AND, OR, XOR)", - "AND groups are equivalent to cardinality groups ranging from 1 to 1, and OR from 1 to n."); - + basicHierarchy("General hierarchial Structure"), + subgroupHierarchy("Hierarchy with supgroups"), + featureDescription("Features with descriptions"), + featureAttributes("Features with attributes"), + featureCardinality("Cardinality of features"), + booleanOperators("Features of boolean operators"), + allOperators("Features of all operators"), + parseable("File can be used for input"); + public final String name; public final String description; @@ -122,7 +124,11 @@ public String toString() { */ @Override public int run(OptionList optionParser) { - +// +// IFormat uvlObject = FeatureModelFormats.getInstance().getFormatList("uvl").get(0); +// System.out.println(uvlObject.getName()); +// formatConversion --input ../formula/src/testFixtures/resources/Automotive02_V1/model.xml --output ../../Desktop/modelWritingTest.uvl --overwrite +// if (!checkIfInputOutputIsPresent(optionParser)) { return 1; } @@ -193,7 +199,7 @@ private static Map> buildSupportedFileExtensions() { */ private void infoLossMessage(String iExt, String oExt) { - String msg = "Info Loss:\n"; + String msg = "Info Loss:" + "\n\t\t\t\t\t\t" + iExt + " --> " + oExt + "\n"; Map> infoLossMap = buildInfoLossMap(); Map iSupports = infoLossMap.get(iExt); // xml @@ -206,13 +212,16 @@ private void infoLossMessage(String iExt, String oExt) { for (FileInfo fileInfo : iSupports.keySet()) { SupportLevel iSupportLevel = iSupports.get(fileInfo); SupportLevel oSupportLevel = oSupports.get(fileInfo); - +// if(oSupportLevel == SupportLevel.NONE && fileInfo == FileInfo.parseable) { +// msg += "\t\t\t" + oExt + " is write only. " + oExt + " can NOT be parsed.\n"; +// } if (oSupportLevel.isLessThan(iSupportLevel)) { - msg += "\t Supports " + fileInfo + "\n \t\t" + iExt + ": " + iSupportLevel + "\n \t\t" + oExt + ": " - + oSupportLevel + "\n"; + // iExt + " --> " + oExt +":\n" + msg += "\t" + fileInfo + " \t\t" + iSupportLevel + "\t" + oSupportLevel + "\n"; + } } - if (!msg.equals("Info Loss:\n")) { + if (!msg.equals("Info Loss:" + "\n\t\t\t\t\t\t" + iExt + " --> " + oExt + "\n")) { FeatJAR.log().warning(msg); } else { FeatJAR.log().message("No Information Loss from " + iExt + " to " + oExt + "."); @@ -226,27 +235,39 @@ private void infoLossMessage(String iExt, String oExt) { private Map> buildInfoLossMap() { Map> supportMap = new HashMap<>(); - + String extension = "xml"; supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); - supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); - + supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.parseable, SupportLevel.FULL); + extension = "uvl"; supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); - supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); - supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); - supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.parseable, SupportLevel.FULL); extension = "dot"; supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - supportMap.get(extension).put(FileInfo.mandatoryAndOptionalFeatures, SupportLevel.NONE); - supportMap.get(extension).put(FileInfo.featureAttributesAndMetadata, SupportLevel.NONE); - supportMap.get(extension).put(FileInfo.hierarchicalFeatureStructure, SupportLevel.PARTIAL); - supportMap.get(extension).put(FileInfo.featureGroups, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.parseable, SupportLevel.NONE); return supportMap; } @@ -259,15 +280,15 @@ private Map> buildInfoLossMap() { */ private boolean checkIfFileExtensionsValid(String inputFileExtension, String outputFileExtension) { if (!supportedInputFileExtensions.contains(inputFileExtension)) { - FeatJAR.log().error("Unsupported input file extension."); - System.out.println("Received extension: " + inputFileExtension + "\n Supported extensions: " + FeatJAR.log().error("Unsupported input file extension.\n" + + "Received extension: " + inputFileExtension + "\nSupported extensions: " + supportedInputFileExtensions); return false; } if (!supportedOutputFileExtensions.contains(outputFileExtension)) { - FeatJAR.log().error("Unsupported output file extension."); - System.out.println("Received extension: " + outputFileExtension + "\n Supported extensions: " + FeatJAR.log().error("Unsupported output file extension.\n" + + "Received extension: " + outputFileExtension + "\nSupported extensions: " + supportedOutputFileExtensions); return false; } @@ -317,18 +338,24 @@ private IFeatureModel inputParser(OptionList optionParser) { */ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { IFormat format; - switch (outputFileExtension) { + + switch (outputFileExtension) { case "xml": format = new XMLFeatureModelFormat(); break; case "dot": format = new GraphVizFeatureModelFormat(); break; + case "uvl": + format = FeatureModelFormats.getInstance().getFormatList("uvl").get(0); + break; default: // this still catches errors if the switch case construct has not implemented all supported file types! FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); return 1; } + + try { if (Files.exists(outputPath)) { if (overWriteOutputFile) { diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index a0caa417..570c3f4e 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -133,13 +133,15 @@ void infoLossMapTestTriggers() throws IOException { String string = new String(byteArray); String expected_output = "Info Loss:\n" - + " Supports Feature attributes and metadata\n" - + " xml: FULL\n" - + " dot: NONE\n" - + " Supports Mandatory and optional features\n" - + " xml: FULL\n" - + " dot: NONE\n" - + ""; + + " xml --> dot\n" + + " General hierarchial Structure FULL NONE\n" + + " Features with descriptions FULL NONE\n" + + " Features with attributes FULL NONE\n" + + " Features of boolean operators FULL NONE\n" + + " File content can be read FULL NONE\n" + + "\n" + + "Output model saved at: model_invalidInput.dot\n" + + ""; assertTrue(string.startsWith(expected_output)); Files.deleteIfExists(Paths.get(outputPath)); From 2ab871a6e846e327cf6373bfaab4a429f723ae48 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 12:04:05 +0200 Subject: [PATCH 125/257] style: renamed SupportLevels: FULL -> YES; NONE -> NO --- .../feature/model/cli/FormatConversion.java | 52 +++++++++---------- 1 file changed, 26 insertions(+), 26 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 0df45d04..fdfe030a 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -68,9 +68,9 @@ public final List> getOptions() { * For info loss map; indicates whether a feature is supported fully, partially, or not at all. */ private enum SupportLevel { - NONE(0), + NO(0), PARTIAL(1), - FULL(2); + YES(2); public final int rank; @@ -238,36 +238,36 @@ private Map> buildInfoLossMap() { String extension = "xml"; supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.NONE); - supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.NONE); - supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.NONE); - supportMap.get(extension).put(FileInfo.parseable, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.NO); + supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.NO); + supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.NO); + supportMap.get(extension).put(FileInfo.parseable, SupportLevel.YES); extension = "uvl"; supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.parseable, SupportLevel.FULL); + supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.parseable, SupportLevel.YES); extension = "dot"; supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.FULL); - supportMap.get(extension).put(FileInfo.parseable, SupportLevel.NONE); + supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.YES); + supportMap.get(extension).put(FileInfo.parseable, SupportLevel.NO); return supportMap; } From 8cbf3fa9725958314050ac0554f3e6b98e1646db Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 12:15:51 +0200 Subject: [PATCH 126/257] fix: updated test t include new string output --- .../featjar/feature/model/cli/FormatConversion.java | 11 ++--------- .../feature/model/cli/FormatConversionTest.java | 7 ++----- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index fdfe030a..f9e497a3 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -124,11 +124,7 @@ public String toString() { */ @Override public int run(OptionList optionParser) { -// -// IFormat uvlObject = FeatureModelFormats.getInstance().getFormatList("uvl").get(0); -// System.out.println(uvlObject.getName()); -// formatConversion --input ../formula/src/testFixtures/resources/Automotive02_V1/model.xml --output ../../Desktop/modelWritingTest.uvl --overwrite -// + if (!checkIfInputOutputIsPresent(optionParser)) { return 1; } @@ -212,11 +208,8 @@ private void infoLossMessage(String iExt, String oExt) { for (FileInfo fileInfo : iSupports.keySet()) { SupportLevel iSupportLevel = iSupports.get(fileInfo); SupportLevel oSupportLevel = oSupports.get(fileInfo); -// if(oSupportLevel == SupportLevel.NONE && fileInfo == FileInfo.parseable) { -// msg += "\t\t\t" + oExt + " is write only. " + oExt + " can NOT be parsed.\n"; -// } + if (oSupportLevel.isLessThan(iSupportLevel)) { - // iExt + " --> " + oExt +":\n" msg += "\t" + fileInfo + " \t\t" + iSupportLevel + "\t" + oSupportLevel + "\n"; } diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 570c3f4e..1483dc7e 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -134,14 +134,11 @@ void infoLossMapTestTriggers() throws IOException { String expected_output = "Info Loss:\n" + " xml --> dot\n" - + " General hierarchial Structure FULL NONE\n" - + " Features with descriptions FULL NONE\n" - + " Features with attributes FULL NONE\n" - + " Features of boolean operators FULL NONE\n" - + " File content can be read FULL NONE\n" + + " File can be used for input YES NO\n" + "\n" + "Output model saved at: model_invalidInput.dot\n" + ""; + assertEquals(string, expected_output); assertTrue(string.startsWith(expected_output)); Files.deleteIfExists(Paths.get(outputPath)); From b0d276af6512d2d42dd606397d4853f547043b2f Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 12:17:38 +0200 Subject: [PATCH 127/257] feat: infolossmap builder now auto-fills unset fileinfos to support level NO --- .../de/featjar/feature/model/cli/FormatConversion.java | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index f9e497a3..bad4de29 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -262,6 +262,13 @@ private Map> buildInfoLossMap() { supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.YES); supportMap.get(extension).put(FileInfo.parseable, SupportLevel.NO); + // if user forgot to set FileInfos: Support Level is automatically set to NONE + for (String ext : supportMap.keySet()) { + for (FileInfo fileInfo : FileInfo.values()) { + supportMap.get(ext).putIfAbsent(fileInfo, SupportLevel.NO); + } + } + return supportMap; } From db7721cbfd46568336260f6844ff46f5eb88d9ec Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 12:41:05 +0200 Subject: [PATCH 128/257] refactor: info loss map is now built in a more structured way, enforcing the format --- .../feature/model/cli/FormatConversion.java | 72 ++++++++++++++++++- 1 file changed, 70 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index bad4de29..3b920cd8 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -225,10 +225,11 @@ private void infoLossMessage(String iExt, String oExt) { * * @return */ + /* private Map> buildInfoLossMap() { Map> supportMap = new HashMap<>(); - + String extension = "xml"; supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.YES); @@ -239,7 +240,7 @@ private Map> buildInfoLossMap() { supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.YES); supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.NO); supportMap.get(extension).put(FileInfo.parseable, SupportLevel.YES); - + extension = "uvl"; supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.YES); @@ -271,6 +272,73 @@ private Map> buildInfoLossMap() { return supportMap; } + */ + + /** + * + * {@return information loss map that tracks how well a file extension supports any given piece of information} + */ + private Map> buildInfoLossMap() { + Map> supportMap = new HashMap<>(); + + buildInfoLossMapRegisterExt("xml", Map.of( + FileInfo.basicHierarchy, SupportLevel.YES, + FileInfo.subgroupHierarchy, SupportLevel.NO, + FileInfo.featureDescription, SupportLevel.YES, + FileInfo.featureAttributes, SupportLevel.YES, + FileInfo.featureCardinality, SupportLevel.NO, + FileInfo.booleanOperators, SupportLevel.YES, + FileInfo.allOperators, SupportLevel.NO, + FileInfo.parseable, SupportLevel.YES + ), supportMap); + + buildInfoLossMapRegisterExt("uvl", Map.of( + FileInfo.basicHierarchy, SupportLevel.YES, + FileInfo.subgroupHierarchy, SupportLevel.YES, + FileInfo.featureDescription, SupportLevel.YES, + FileInfo.featureAttributes, SupportLevel.YES, + FileInfo.featureCardinality, SupportLevel.YES, + FileInfo.booleanOperators, SupportLevel.YES, + FileInfo.allOperators, SupportLevel.YES, + FileInfo.parseable, SupportLevel.YES + ), supportMap); + + + buildInfoLossMapRegisterExt("dot", Map.of( + FileInfo.basicHierarchy, SupportLevel.YES, + FileInfo.subgroupHierarchy, SupportLevel.YES, + FileInfo.featureDescription, SupportLevel.YES, + FileInfo.featureAttributes, SupportLevel.YES, + FileInfo.featureCardinality, SupportLevel.YES, + FileInfo.booleanOperators, SupportLevel.YES, + FileInfo.allOperators, SupportLevel.YES, + FileInfo.parseable, SupportLevel.NO + ), supportMap); + + + // if user forgot to set FileInfos: Support Level is automatically set to NONE + for (String ext : supportMap.keySet()) { + for (FileInfo fileInfo : FileInfo.values()) { + supportMap.get(ext).putIfAbsent(fileInfo, SupportLevel.NO); + } + } + + return supportMap; + } + + /** + * Reinforces correct addition of infoLossMap entries + * @param extension file extension that will be added + * @param fileInfos pieces of file information as described in FileInfo enum + * @param supportMap the information loss map that's being updated + */ + private void buildInfoLossMapRegisterExt(String extension, Map fileInfos, Map> supportMap) { + if (fileInfos.size() != FileInfo.values().length) { + FeatJAR.log().error("Info Loss Map: " + extension + " was added with too many or too few FileInfos. Skipping this extension."); + return; + } + supportMap.put(extension, new EnumMap<>(fileInfos)); + } /** * Checks if input and output file extensions provided by user appear in list of supported extensions. From 8be7f5fbf8ff7de0c9d058907a4b917bd71fd68d Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 12:52:02 +0200 Subject: [PATCH 129/257] feat: prototypical implementation of extension point based output format recognition --- .../de/featjar/feature/model/cli/FormatConversion.java | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 3b920cd8..e8603013 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -35,6 +35,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; /** * Prints statistics about a provided Feature Model. @@ -406,6 +407,14 @@ private IFeatureModel inputParser(OptionList optionParser) { */ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { IFormat format; + + List> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) + .collect(Collectors.toList()); + + System.out.println(outputFormats); + switch (outputFileExtension) { case "xml": From 45ea170228f743af22100bd057f4594735ca5955 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 12:52:06 +0200 Subject: [PATCH 130/257] style: removed commented out content, applied spotlessapply --- .../feature/model/cli/FormatConversion.java | 184 +++++++----------- .../model/cli/FormatConversionTest.java | 12 +- 2 files changed, 75 insertions(+), 121 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 3b920cd8..dba823b2 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -88,15 +88,15 @@ boolean isLessThan(SupportLevel other) { * Saving name as well as a description in case we need to explain it to the user later. */ private enum FileInfo { - basicHierarchy("General hierarchial Structure"), - subgroupHierarchy("Hierarchy with supgroups"), - featureDescription("Features with descriptions"), - featureAttributes("Features with attributes"), - featureCardinality("Cardinality of features"), - booleanOperators("Features of boolean operators"), - allOperators("Features of all operators"), - parseable("File can be used for input"); - + basicHierarchy("General hierarchial Structure"), + subgroupHierarchy("Hierarchy with supgroups"), + featureDescription("Features with descriptions"), + featureAttributes("Features with attributes"), + featureCardinality("Cardinality of features"), + booleanOperators("Features of boolean operators"), + allOperators("Features of all operators"), + parseable("File can be used for input"); + public final String name; public final String description; @@ -124,7 +124,7 @@ public String toString() { */ @Override public int run(OptionList optionParser) { - + if (!checkIfInputOutputIsPresent(optionParser)) { return 1; } @@ -195,7 +195,7 @@ private static Map> buildSupportedFileExtensions() { */ private void infoLossMessage(String iExt, String oExt) { - String msg = "Info Loss:" + "\n\t\t\t\t\t\t" + iExt + " --> " + oExt + "\n"; + String msg = "Info Loss:" + "\n\t\t\t\t\t\t" + iExt + " --> " + oExt + "\n"; Map> infoLossMap = buildInfoLossMap(); Map iSupports = infoLossMap.get(iExt); // xml @@ -211,69 +211,15 @@ private void infoLossMessage(String iExt, String oExt) { if (oSupportLevel.isLessThan(iSupportLevel)) { msg += "\t" + fileInfo + " \t\t" + iSupportLevel + "\t" + oSupportLevel + "\n"; - } } - if (!msg.equals("Info Loss:" + "\n\t\t\t\t\t\t" + iExt + " --> " + oExt + "\n")) { + if (!msg.equals("Info Loss:" + "\n\t\t\t\t\t\t" + iExt + " --> " + oExt + "\n")) { FeatJAR.log().warning(msg); } else { FeatJAR.log().message("No Information Loss from " + iExt + " to " + oExt + "."); } } - /** - * - * @return - */ - /* - private Map> buildInfoLossMap() { - - Map> supportMap = new HashMap<>(); - - String extension = "xml"; - supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.NO); - supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.NO); - supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.NO); - supportMap.get(extension).put(FileInfo.parseable, SupportLevel.YES); - - extension = "uvl"; - supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.parseable, SupportLevel.YES); - - extension = "dot"; - supportMap.put(extension, new EnumMap<>(FileInfo.class)); // for each extension: add each feature - supportMap.get(extension).put(FileInfo.basicHierarchy, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.subgroupHierarchy, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.featureDescription, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.featureAttributes, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.featureCardinality, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.booleanOperators, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.allOperators, SupportLevel.YES); - supportMap.get(extension).put(FileInfo.parseable, SupportLevel.NO); - - // if user forgot to set FileInfos: Support Level is automatically set to NONE - for (String ext : supportMap.keySet()) { - for (FileInfo fileInfo : FileInfo.values()) { - supportMap.get(ext).putIfAbsent(fileInfo, SupportLevel.NO); - } - } - - return supportMap; - } - */ - /** * * {@return information loss map that tracks how well a file extension supports any given piece of information} @@ -281,40 +227,44 @@ private Map> buildInfoLossMap() { private Map> buildInfoLossMap() { Map> supportMap = new HashMap<>(); - buildInfoLossMapRegisterExt("xml", Map.of( - FileInfo.basicHierarchy, SupportLevel.YES, - FileInfo.subgroupHierarchy, SupportLevel.NO, - FileInfo.featureDescription, SupportLevel.YES, - FileInfo.featureAttributes, SupportLevel.YES, - FileInfo.featureCardinality, SupportLevel.NO, - FileInfo.booleanOperators, SupportLevel.YES, - FileInfo.allOperators, SupportLevel.NO, - FileInfo.parseable, SupportLevel.YES - ), supportMap); - - buildInfoLossMapRegisterExt("uvl", Map.of( - FileInfo.basicHierarchy, SupportLevel.YES, - FileInfo.subgroupHierarchy, SupportLevel.YES, - FileInfo.featureDescription, SupportLevel.YES, - FileInfo.featureAttributes, SupportLevel.YES, - FileInfo.featureCardinality, SupportLevel.YES, - FileInfo.booleanOperators, SupportLevel.YES, - FileInfo.allOperators, SupportLevel.YES, - FileInfo.parseable, SupportLevel.YES - ), supportMap); - - - buildInfoLossMapRegisterExt("dot", Map.of( - FileInfo.basicHierarchy, SupportLevel.YES, - FileInfo.subgroupHierarchy, SupportLevel.YES, - FileInfo.featureDescription, SupportLevel.YES, - FileInfo.featureAttributes, SupportLevel.YES, - FileInfo.featureCardinality, SupportLevel.YES, - FileInfo.booleanOperators, SupportLevel.YES, - FileInfo.allOperators, SupportLevel.YES, - FileInfo.parseable, SupportLevel.NO - ), supportMap); - + buildInfoLossMapRegisterExt( + "xml", + Map.of( + FileInfo.basicHierarchy, SupportLevel.YES, + FileInfo.subgroupHierarchy, SupportLevel.NO, + FileInfo.featureDescription, SupportLevel.YES, + FileInfo.featureAttributes, SupportLevel.YES, + FileInfo.featureCardinality, SupportLevel.NO, + FileInfo.booleanOperators, SupportLevel.YES, + FileInfo.allOperators, SupportLevel.NO, + FileInfo.parseable, SupportLevel.YES), + supportMap); + + buildInfoLossMapRegisterExt( + "uvl", + Map.of( + FileInfo.basicHierarchy, SupportLevel.YES, + FileInfo.subgroupHierarchy, SupportLevel.YES, + FileInfo.featureDescription, SupportLevel.YES, + FileInfo.featureAttributes, SupportLevel.YES, + FileInfo.featureCardinality, SupportLevel.YES, + FileInfo.booleanOperators, SupportLevel.YES, + FileInfo.allOperators, SupportLevel.YES, + FileInfo.parseable, SupportLevel.YES), + supportMap); + + buildInfoLossMapRegisterExt( + "dot", + Map.of( + FileInfo.basicHierarchy, SupportLevel.YES, + FileInfo.subgroupHierarchy, SupportLevel.YES, + FileInfo.featureDescription, SupportLevel.YES, + FileInfo.featureAttributes, SupportLevel.YES, + FileInfo.featureCardinality, SupportLevel.YES, + FileInfo.booleanOperators, SupportLevel.YES, + FileInfo.allOperators, SupportLevel.YES, + FileInfo.parseable, SupportLevel.NO), + supportMap); // if user forgot to set FileInfos: Support Level is automatically set to NONE for (String ext : supportMap.keySet()) { @@ -332,9 +282,14 @@ private Map> buildInfoLossMap() { * @param fileInfos pieces of file information as described in FileInfo enum * @param supportMap the information loss map that's being updated */ - private void buildInfoLossMapRegisterExt(String extension, Map fileInfos, Map> supportMap) { + private void buildInfoLossMapRegisterExt( + String extension, + Map fileInfos, + Map> supportMap) { if (fileInfos.size() != FileInfo.values().length) { - FeatJAR.log().error("Info Loss Map: " + extension + " was added with too many or too few FileInfos. Skipping this extension."); + FeatJAR.log() + .error("Info Loss Map: " + extension + + " was added with too many or too few FileInfos. Skipping this extension."); return; } supportMap.put(extension, new EnumMap<>(fileInfos)); @@ -348,16 +303,18 @@ private void buildInfoLossMapRegisterExt(String extension, Map format; - - switch (outputFileExtension) { + + switch (outputFileExtension) { case "xml": format = new XMLFeatureModelFormat(); break; @@ -415,15 +372,14 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten format = new GraphVizFeatureModelFormat(); break; case "uvl": - format = FeatureModelFormats.getInstance().getFormatList("uvl").get(0); - break; + format = FeatureModelFormats.getInstance().getFormatList("uvl").get(0); + break; default: // this still catches errors if the switch case construct has not implemented all supported file types! FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); return 1; } - - + try { if (Files.exists(outputPath)) { if (overWriteOutputFile) { diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 1483dc7e..a56af9ff 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -118,7 +118,6 @@ void infoLossMapTestTriggers() throws IOException { Files.deleteIfExists(Paths.get(outputPath)); - // Using FeatJAR logger ByteArrayOutputStream out = new ByteArrayOutputStream(); PrintStream stream = new PrintStream(out); @@ -128,16 +127,15 @@ void infoLossMapTestTriggers() throws IOException { FeatJAR.initialize(config); FeatJAR.runInternally("formatConversion", "--input", inputPath, "--output", outputPath); - byte[] byteArray = out.toByteArray(); String string = new String(byteArray); String expected_output = "Info Loss:\n" - + " xml --> dot\n" - + " File can be used for input YES NO\n" - + "\n" - + "Output model saved at: model_invalidInput.dot\n" - + ""; + + " xml --> dot\n" + + " File can be used for input YES NO\n" + + "\n" + + "Output model saved at: model_invalidInput.dot\n" + + ""; assertEquals(string, expected_output); assertTrue(string.startsWith(expected_output)); From ac3cf17b4604d95ea66fe429d59076d16e8a68a4 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 12:53:34 +0200 Subject: [PATCH 131/257] feat: prototypical implementation of extension point based output format recognition --- .../java/de/featjar/feature/model/cli/FormatConversion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index efabae18..f43f9c93 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -370,7 +370,7 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) .collect(Collectors.toList()); - System.out.println(outputFormats); + System.out.println(outputFormats); // switch (outputFileExtension) { From 57e69714517bc52896c35e1e4b75a5cf2825b8d7 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 13:08:45 +0200 Subject: [PATCH 132/257] style: minimal minor clean up --- .../feature/model/cli/FormatConversion.java | 37 ++++++------------- 1 file changed, 11 insertions(+), 26 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index f43f9c93..ac994a5f 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -29,8 +29,6 @@ import de.featjar.base.io.format.IFormat; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.io.FeatureModelFormats; -import de.featjar.feature.model.io.xml.GraphVizFeatureModelFormat; -import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -363,30 +361,17 @@ private IFeatureModel inputParser(OptionList optionParser) { * @return */ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { - IFormat format; - - List> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() - .filter(IFormat::supportsWrite) - .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) - .collect(Collectors.toList()); - - System.out.println(outputFormats); // - - - switch (outputFileExtension) { - case "xml": - format = new XMLFeatureModelFormat(); - break; - case "dot": - format = new GraphVizFeatureModelFormat(); - break; - case "uvl": - format = FeatureModelFormats.getInstance().getFormatList("uvl").get(0); - break; - default: - // this still catches errors if the switch case construct has not implemented all supported file types! - FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); - return 1; + + IFormat format = null; + + try { + List> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) + .collect(Collectors.toList()); + format = outputFormats.get(0); + } catch (IndexOutOfBoundsException e) { + FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); } try { From 265c1dca01e19b87b4be94342b2b1a44c4af75e6 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 13:12:10 +0200 Subject: [PATCH 133/257] feat: null check for supported file extensions --- .../feature/model/cli/FormatConversion.java | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index ac994a5f..d81cbced 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -172,12 +172,14 @@ private static Map> buildSupportedFileExtensions() { List supportedInputFileExtensions = new ArrayList<>(); List supportedOutputFileExtensions = new ArrayList<>(); - for (IFormat ext : supportedFileExtensions) { - if (ext.supportsParse()) { - supportedInputFileExtensions.add(ext.getFileExtension()); - } - if (ext.supportsWrite()) { - supportedOutputFileExtensions.add(ext.getFileExtension()); + if (supportedFileExtensions != null) { + for (IFormat ext : supportedFileExtensions) { + if (ext.supportsParse()) { + supportedInputFileExtensions.add(ext.getFileExtension()); + } + if (ext.supportsWrite()) { + supportedOutputFileExtensions.add(ext.getFileExtension()); + } } } From 0c14556d9227c0a4feedac3a28d345e87b0d4b1a Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 13:19:54 +0200 Subject: [PATCH 134/257] doc: added remaining method documentations --- .../feature/model/cli/FormatConversion.java | 45 +++++++++---------- 1 file changed, 22 insertions(+), 23 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index d81cbced..8e62e18d 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -117,9 +117,9 @@ public String toString() { /** * main function for handling format conversion - * @param OptionParser supplied by command line execution. + * @param optionParser supplied by command line execution. * - * @return 0 if success, 1 if input/output paths are invalid, 2 if IOException, 3 if no model could be parsed from input file. + * @return 0 on success, 1 if a file was already present and should not have been overwritten, 2 on IOException */ @Override public int run(OptionList optionParser) { @@ -189,18 +189,17 @@ private static Map> buildSupportedFileExtensions() { } /** - * - * @param iExt - * @param oExt - * @return 0 for no information loss. 1 for information loss, 2 on error due to unsupported input or + * Informs user about potential information loss occurring during file conversion + * @param inputFileExtension file extension of the input file (lower case, no leading dot) + * @param outputFileExtension file extension of the output file (lower case, no leading dot) */ - private void infoLossMessage(String iExt, String oExt) { + private void infoLossMessage(String inputFileExtension, String outputFileExtension) { - String msg = "Info Loss:" + "\n\t\t\t\t\t\t" + iExt + " --> " + oExt + "\n"; + String msg = "Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n"; Map> infoLossMap = buildInfoLossMap(); - Map iSupports = infoLossMap.get(iExt); // xml - Map oSupports = infoLossMap.get(oExt); + Map iSupports = infoLossMap.get(inputFileExtension); + Map oSupports = infoLossMap.get(outputFileExtension); if (iSupports == null || oSupports == null) { return; @@ -214,10 +213,10 @@ private void infoLossMessage(String iExt, String oExt) { msg += "\t" + fileInfo + " \t\t" + iSupportLevel + "\t" + oSupportLevel + "\n"; } } - if (!msg.equals("Info Loss:" + "\n\t\t\t\t\t\t" + iExt + " --> " + oExt + "\n")) { + if (!msg.equals("Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n")) { FeatJAR.log().warning(msg); } else { - FeatJAR.log().message("No Information Loss from " + iExt + " to " + oExt + "."); + FeatJAR.log().message("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); } } @@ -323,8 +322,8 @@ private boolean checkIfFileExtensionsValid(String inputFileExtension, String out /** * - * @param optionParser - * @return + * @param optionParser holds the command line parameters + * {@return true if an input and output path were provided, otherwise false} */ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { if (!optionParser.getResult(INPUT_OPTION).isPresent()) { @@ -338,9 +337,9 @@ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { } /** - * - * @param optionParser - * @return + * Attempts to extract a feature model from the input file. + * @param optionParser holds the command line parameters + * @return Feature Model read out from input file. Will be null on failure. */ private IFeatureModel inputParser(OptionList optionParser) { Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); @@ -355,12 +354,12 @@ private IFeatureModel inputParser(OptionList optionParser) { } /** - * - * @param outputPath - * @param model - * @param outputFileExtension - * @param overWriteOutputFile - * @return + * Saves the read feature model as the desired output file. Automatically fetches the appropriate format. Does error handling. + * @param outputPath Full path to output file. + * @param model Feature Model to be saved into the output file. + * @param outputFileExtension extension of the output file. Used to fetch appropriate format. + * @param overWriteOutputFile flag that decides whether existing output files with the same name should be overwritten. + * @return 0 on success, 1 if a file was already present and should not have been overwritten, 2 on IOException */ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { From ad0f320f8d186d34bbe138b5902f04bd05da6fdd Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 13:31:14 +0200 Subject: [PATCH 135/257] style: improved documentation --- .../java/de/featjar/feature/model/cli/FormatConversion.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 8e62e18d..dd2c598e 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -51,7 +51,7 @@ public class FormatConversion implements ICommand { .setValidator(Option.PathValidator); public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) - .setDescription("Path to output file. Accepted File Types: " + supportedInputFileExtensions); + .setDescription("Path to output file. Accepted File Types: " + supportedOutputFileExtensions); public static final Option OVERWRITE = Option.newFlag("overwrite").setDescription("Overwrite output file."); @@ -216,7 +216,8 @@ private void infoLossMessage(String inputFileExtension, String outputFileExtensi if (!msg.equals("Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n")) { FeatJAR.log().warning(msg); } else { - FeatJAR.log().message("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); + FeatJAR.log() + .message("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); } } From 7c8fd1996d608398ba210472adf7f40a14c23d3e Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 13:31:26 +0200 Subject: [PATCH 136/257] doc: fixed authorship in docs --- .../feature/model/analysis/visitor/FeatureTreeGroupCounter.java | 2 +- .../feature/model/analysis/visitor/TreeAvgChildrenCounter.java | 2 +- .../featjar/feature/model/analysis/visitor/TreeLeafCounter.java | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index 0b62d699..fcae6df4 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -31,7 +31,7 @@ /** * Counts the share of groups found in the given feature tree, in order: AlternativeGroup, OrGroup, AndGroup, OtherGroup. * - * @author Sebastian Krieter + * @author Benjamin von Holt */ public class FeatureTreeGroupCounter implements ITreeVisitor, HashMap> { int altCounter = 0, orCounter = 0, andCounter = 0, otherCounter = 0; diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index 0b716b4a..a339c4c3 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -29,7 +29,7 @@ * Calculates the average amount of children per node in the tree. * Returns 0 if tree has no nodes. * - * @author Sebastian Krieter + * @author Benjamin von Holt */ public class TreeAvgChildrenCounter implements ITreeVisitor, Double> { private int nodeCount = 0; diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java index 4e2453fe..d158de51 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java @@ -29,7 +29,7 @@ * Counts the number of nodes that have no child nodes * Can be passed a class up to which should be counted (e.g., to exclude details in a tree). * - * @author Sebastian Krieter + * @author Benjamin von Holt */ public class TreeLeafCounter implements ITreeVisitor, Integer> { private int leafCount = 0; From 0bb811de9b0c708b4bed7cc2b351eab38790e6b5 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 13:36:25 +0200 Subject: [PATCH 137/257] fix: various small fixes --- .../feature/model/cli/FormatConversion.java | 26 +++++++------------ 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index dd2c598e..c9c2bc0e 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -42,9 +42,9 @@ */ public class FormatConversion implements ICommand { - private static Map> supportedFileExtensions = buildSupportedFileExtensions(); - private static List supportedInputFileExtensions = supportedFileExtensions.get("input"); - private static List supportedOutputFileExtensions = supportedFileExtensions.get("output"); + private static final Map> supportedFileExtensions = buildSupportedFileExtensions(); + private static final List supportedInputFileExtensions = supportedFileExtensions.get("input"); + private static final List supportedOutputFileExtensions = supportedFileExtensions.get("output"); public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) @@ -68,8 +68,7 @@ public final List> getOptions() { */ private enum SupportLevel { NO(0), - PARTIAL(1), - YES(2); + YES(1); public final int rank; @@ -87,8 +86,8 @@ boolean isLessThan(SupportLevel other) { * Saving name as well as a description in case we need to explain it to the user later. */ private enum FileInfo { - basicHierarchy("General hierarchial Structure"), - subgroupHierarchy("Hierarchy with supgroups"), + basicHierarchy("General hierarchical Structure"), + subgroupHierarchy("Hierarchy with subgroups"), featureDescription("Features with descriptions"), featureAttributes("Features with attributes"), featureCardinality("Cardinality of features"), @@ -97,21 +96,14 @@ private enum FileInfo { parseable("File can be used for input"); public final String name; - public final String description; FileInfo(String name) { this.name = name; - this.description = ""; - } - - FileInfo(String name, String description) { - this.name = name; - this.description = description; } @Override public String toString() { - return description.isEmpty() ? name : name + ": " + description; + return name; } } @@ -153,7 +145,7 @@ public int run(OptionList optionParser) { } /** Iterates over an extension point to compile lists of the supported file extensions. - * @return One list that contains all supported input file extensions (under the key: "input"), and one list that contains all supported output file extensions (key: "output". + * @return One list that contains all supported input file extensions (under the key: "input"), and one list that contains all supported output file extensions (key: "output"). */ private static Map> buildSupportedFileExtensions() { @@ -381,7 +373,7 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten if (overWriteOutputFile) { FeatJAR.log() .message("File already present at: " + outputPath + "\n\tContinuing to overwrite File."); - } else if (!overWriteOutputFile) { + } else { FeatJAR.log() .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath + "\n\tTo overwrite present file add --overwrite"); From b15f5870465fb2fc66c81240410a55e403ecacb7 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 13:37:56 +0200 Subject: [PATCH 138/257] doc | refactor: small fixes --- .../de/featjar/feature/model/cli/FormatConversionTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index a56af9ff..05f8b7ad 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -39,7 +39,6 @@ import org.junit.jupiter.api.Test; /** - * @throws IOException * @author Knut, Kilian & Benjamin */ public class FormatConversionTest { @@ -134,9 +133,8 @@ void infoLossMapTestTriggers() throws IOException { + " xml --> dot\n" + " File can be used for input YES NO\n" + "\n" - + "Output model saved at: model_invalidInput.dot\n" - + ""; - assertEquals(string, expected_output); + + "Output model saved at: model_invalidInput.dot\n"; + assertEquals(expected_output, string); assertTrue(string.startsWith(expected_output)); Files.deleteIfExists(Paths.get(outputPath)); From e07a11aa05304f7f5fd8fa5a3e64cb08ee2539fe Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 13:43:24 +0200 Subject: [PATCH 139/257] refactor: implemented StringBuilder instead of String --- .../de/featjar/feature/model/cli/FormatConversion.java | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index c9c2bc0e..bc7643cb 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -187,7 +187,8 @@ private static Map> buildSupportedFileExtensions() { */ private void infoLossMessage(String inputFileExtension, String outputFileExtension) { - String msg = "Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n"; + StringBuilder msg = new StringBuilder( + "Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n"); Map> infoLossMap = buildInfoLossMap(); Map iSupports = infoLossMap.get(inputFileExtension); @@ -202,11 +203,12 @@ private void infoLossMessage(String inputFileExtension, String outputFileExtensi SupportLevel oSupportLevel = oSupports.get(fileInfo); if (oSupportLevel.isLessThan(iSupportLevel)) { - msg += "\t" + fileInfo + " \t\t" + iSupportLevel + "\t" + oSupportLevel + "\n"; + msg.append("\t" + fileInfo + " \t\t" + iSupportLevel + "\t" + oSupportLevel + "\n"); } } - if (!msg.equals("Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n")) { - FeatJAR.log().warning(msg); + if (!msg.toString() + .equals("Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n")) { + FeatJAR.log().warning(msg.toString()); } else { FeatJAR.log() .message("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); From a80692d65b50f3cfa20d9b4a984951f4475c3ec7 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 15:01:20 +0200 Subject: [PATCH 140/257] test: added more tests --- .../feature/model/cli/FormatConversion.java | 56 +++++++++++++------ .../model/cli/FormatConversionTest.java | 31 +++++++++- .../model/cli/resources/emptyModel.xml | 0 3 files changed, 68 insertions(+), 19 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/emptyModel.xml diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index bc7643cb..b938e613 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -111,14 +111,21 @@ public String toString() { * main function for handling format conversion * @param optionParser supplied by command line execution. * - * @return 0 on success, 1 if a file was already present and should not have been overwritten, 2 on IOException + * @return 0 on success + * 1 if output/input aren't present + * 2 if input/output file type is invalid + * 3 if the model could not be parsed, + * 4 if a file is already present at output path and no overwrite is specified + * 5 on IOException */ @Override public int run(OptionList optionParser) { if (!checkIfInputOutputIsPresent(optionParser)) { + System.out.println("HERE"); return 1; } + Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); // check if provided file extensions are supported String inputFileExtension = @@ -139,7 +146,6 @@ public int run(OptionList optionParser) { return 3; } - Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); } @@ -149,9 +155,19 @@ public int run(OptionList optionParser) { */ private static Map> buildSupportedFileExtensions() { - if (!FeatJAR.isInitialized()) { - FeatJAR.initialize(); - } +// if (!FeatJAR.isInitialized()) { +// FeatJAR.initialize(); +// } + +// FeatureModelFormats.getInstance().getExtensions() +// .stream() +// .filter(ext -> ext.supportsParse()) +// .collect(Collectors.toList()); +// +// FeatureModelFormats.getInstance().getExtensions() +// .stream() +// .filter(ext -> ext.supportsWrite()) +// .collect(Collectors.toList()); List> supportedFileExtensions = null; @@ -211,7 +227,7 @@ private void infoLossMessage(String inputFileExtension, String outputFileExtensi FeatJAR.log().warning(msg.toString()); } else { FeatJAR.log() - .message("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); + .info("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); } } @@ -322,9 +338,11 @@ private boolean checkIfFileExtensionsValid(String inputFileExtension, String out */ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { if (!optionParser.getResult(INPUT_OPTION).isPresent()) { + System.out.println("HERE1"); FeatJAR.log().error("No input path provided."); return false; } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { + System.out.println("HERE2"); FeatJAR.log().error("No output path provided."); return false; } @@ -354,39 +372,43 @@ private IFeatureModel inputParser(OptionList optionParser) { * @param model Feature Model to be saved into the output file. * @param outputFileExtension extension of the output file. Used to fetch appropriate format. * @param overWriteOutputFile flag that decides whether existing output files with the same name should be overwritten. - * @return 0 on success, 1 if a file was already present and should not have been overwritten, 2 on IOException + * @return 0 on success + * 2 if an input/output file type is invalid + * 4 if a file is already present at output path and no overwrite is specified + * 5 on IOException */ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { IFormat format = null; - try { - List> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() + Optional> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() .filter(IFormat::supportsWrite) .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) - .collect(Collectors.toList()); - format = outputFormats.get(0); - } catch (IndexOutOfBoundsException e) { - FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); - } + .findFirst(); + if (outputFormats.isEmpty()) { + FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); + return 2; + } else { + format = outputFormats.get(); + } try { if (Files.exists(outputPath)) { if (overWriteOutputFile) { FeatJAR.log() - .message("File already present at: " + outputPath + "\n\tContinuing to overwrite File."); + .info("File already present at: " + outputPath + "\n\tContinuing to overwrite File."); } else { FeatJAR.log() .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath + "\n\tTo overwrite present file add --overwrite"); - return 1; + return 4; } } IO.save(model, outputPath, format); } catch (IOException e) { FeatJAR.log().error(e); - return 2; + return 5; } FeatJAR.log().message("Output model saved at: " + outputPath); return 0; diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 05f8b7ad..d0d7721e 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -75,10 +75,10 @@ void fileWritingTest() throws IOException { * */ @Test - void invalidInput() throws IOException { + void inputNotPresent() throws IOException { inputPath = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; - outputPath = "model_invalidInput.xml"; + outputPath = "model_inputNotPresent.xml"; Files.deleteIfExists(Paths.get(outputPath)); @@ -105,6 +105,33 @@ void invalidOutput() throws IOException { Files.deleteIfExists(Paths.get(outputPath)); } + /** + * + * + */ + @Test + void modelNotParsed() throws IOException { + + inputPath = "src/test/java/de/featjar/feature/model/cli/resources/emptyModel.xml"; + outputPath = "model_modelNotParsed.xml"; + + int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); + assertEquals(3, exit_code); + + } + + /** + * + * + */ +// @Test +// void modelPresentNoOverwrite() throws IOException { +// +// int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", inputPath); +// assertEquals(4, exit_code); +// +// } + /** * * checks if an info loss message is produced diff --git a/src/test/java/de/featjar/feature/model/cli/resources/emptyModel.xml b/src/test/java/de/featjar/feature/model/cli/resources/emptyModel.xml new file mode 100644 index 00000000..e69de29b From 9b8257b6310950335c3018f6aeb94784c1477631 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 15:09:13 +0200 Subject: [PATCH 141/257] fix: tests are working now --- .../feature/model/cli/FormatConversion.java | 62 +++++++++---------- .../model/cli/FormatConversionTest.java | 40 ++++++------ 2 files changed, 50 insertions(+), 52 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index b938e613..dc7cd3cf 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -33,7 +33,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; -import java.util.stream.Collectors; /** * Prints statistics about a provided Feature Model. @@ -114,7 +113,7 @@ public String toString() { * @return 0 on success * 1 if output/input aren't present * 2 if input/output file type is invalid - * 3 if the model could not be parsed, + * 3 if the model could not be parsed, * 4 if a file is already present at output path and no overwrite is specified * 5 on IOException */ @@ -122,7 +121,7 @@ public String toString() { public int run(OptionList optionParser) { if (!checkIfInputOutputIsPresent(optionParser)) { - System.out.println("HERE"); + System.out.println("HERE"); return 1; } Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); @@ -146,7 +145,6 @@ public int run(OptionList optionParser) { return 3; } - return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); } @@ -155,19 +153,19 @@ public int run(OptionList optionParser) { */ private static Map> buildSupportedFileExtensions() { -// if (!FeatJAR.isInitialized()) { -// FeatJAR.initialize(); -// } - -// FeatureModelFormats.getInstance().getExtensions() -// .stream() -// .filter(ext -> ext.supportsParse()) -// .collect(Collectors.toList()); -// -// FeatureModelFormats.getInstance().getExtensions() -// .stream() -// .filter(ext -> ext.supportsWrite()) -// .collect(Collectors.toList()); + // if (!FeatJAR.isInitialized()) { + // FeatJAR.initialize(); + // } + + // FeatureModelFormats.getInstance().getExtensions() + // .stream() + // .filter(ext -> ext.supportsParse()) + // .collect(Collectors.toList()); + // + // FeatureModelFormats.getInstance().getExtensions() + // .stream() + // .filter(ext -> ext.supportsWrite()) + // .collect(Collectors.toList()); List> supportedFileExtensions = null; @@ -226,8 +224,7 @@ private void infoLossMessage(String inputFileExtension, String outputFileExtensi .equals("Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n")) { FeatJAR.log().warning(msg.toString()); } else { - FeatJAR.log() - .info("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); + FeatJAR.log().info("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); } } @@ -338,11 +335,11 @@ private boolean checkIfFileExtensionsValid(String inputFileExtension, String out */ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { if (!optionParser.getResult(INPUT_OPTION).isPresent()) { - System.out.println("HERE1"); + System.out.println("HERE1"); FeatJAR.log().error("No input path provided."); return false; } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { - System.out.println("HERE2"); + System.out.println("HERE2"); FeatJAR.log().error("No output path provided."); return false; } @@ -381,22 +378,21 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten IFormat format = null; - Optional> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() - .filter(IFormat::supportsWrite) - .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) - .findFirst(); - if (outputFormats.isEmpty()) { - FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); - return 2; - } else { - format = outputFormats.get(); - } + Optional> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) + .findFirst(); + if (outputFormats.isEmpty()) { + FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); + return 2; + } else { + format = outputFormats.get(); + } try { if (Files.exists(outputPath)) { if (overWriteOutputFile) { - FeatJAR.log() - .info("File already present at: " + outputPath + "\n\tContinuing to overwrite File."); + FeatJAR.log().info("File already present at: " + outputPath + "\n\tContinuing to overwrite File."); } else { FeatJAR.log() .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index d0d7721e..d59a8ac5 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -106,32 +106,30 @@ void invalidOutput() throws IOException { } /** - * - * - */ + * + * + */ @Test void modelNotParsed() throws IOException { - - inputPath = "src/test/java/de/featjar/feature/model/cli/resources/emptyModel.xml"; + + inputPath = "src/test/java/de/featjar/feature/model/cli/resources/emptyModel.xml"; outputPath = "model_modelNotParsed.xml"; - + int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); assertEquals(3, exit_code); - } - + /** - * - * - */ -// @Test -// void modelPresentNoOverwrite() throws IOException { -// -// int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", inputPath); -// assertEquals(4, exit_code); -// -// } - + * + * + */ + @Test + void modelPresentNoOverwrite() throws IOException { + + int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", inputPath); + assertEquals(4, exit_code); + } + /** * * checks if an info loss message is produced @@ -165,6 +163,7 @@ void infoLossMapTestTriggers() throws IOException { assertTrue(string.startsWith(expected_output)); Files.deleteIfExists(Paths.get(outputPath)); + FeatJAR.deinitialize(); } /** @@ -174,6 +173,8 @@ void infoLossMapTestTriggers() throws IOException { @Test void testWriteAndOverwrite() throws IOException { + FeatJAR.initialize(); + Path outputPath = Paths.get("model_testWriteAndOverwrite.xml"); FeatureModel model = generateModel(); @@ -189,5 +190,6 @@ void testWriteAndOverwrite() throws IOException { assertEquals(model, retrievedModel); Files.deleteIfExists(outputPath); + FeatJAR.deinitialize(); } } From ac587503834791bdfe5aad10b094ffc5e4b81bda Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 15:12:41 +0200 Subject: [PATCH 142/257] refactor: replaced supported file format builder with stream solution --- .../feature/model/cli/FormatConversion.java | 61 ++++--------------- 1 file changed, 12 insertions(+), 49 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index dc7cd3cf..6d6bdfa7 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -33,6 +33,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.*; +import java.util.stream.Collectors; /** * Prints statistics about a provided Feature Model. @@ -41,9 +42,17 @@ */ public class FormatConversion implements ICommand { - private static final Map> supportedFileExtensions = buildSupportedFileExtensions(); - private static final List supportedInputFileExtensions = supportedFileExtensions.get("input"); - private static final List supportedOutputFileExtensions = supportedFileExtensions.get("output"); + private static final List supportedInputFileExtensions = FeatureModelFormats.getInstance().getExtensions() + .stream() + .filter(IFormat::supportsParse) + .map(IFormat::getFileExtension) + .collect(Collectors.toList()); + + private static final List supportedOutputFileExtensions = FeatureModelFormats.getInstance().getExtensions() + .stream() + .filter(IFormat::supportsWrite) + .map(IFormat::getFileExtension) + .collect(Collectors.toList()); public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) @@ -148,52 +157,6 @@ public int run(OptionList optionParser) { return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); } - /** Iterates over an extension point to compile lists of the supported file extensions. - * @return One list that contains all supported input file extensions (under the key: "input"), and one list that contains all supported output file extensions (key: "output"). - */ - private static Map> buildSupportedFileExtensions() { - - // if (!FeatJAR.isInitialized()) { - // FeatJAR.initialize(); - // } - - // FeatureModelFormats.getInstance().getExtensions() - // .stream() - // .filter(ext -> ext.supportsParse()) - // .collect(Collectors.toList()); - // - // FeatureModelFormats.getInstance().getExtensions() - // .stream() - // .filter(ext -> ext.supportsWrite()) - // .collect(Collectors.toList()); - - List> supportedFileExtensions = null; - - try { - supportedFileExtensions = FeatureModelFormats.getInstance().getExtensions(); - } catch (Exception e) { - FeatJAR.log().error(e); - } - - List supportedInputFileExtensions = new ArrayList<>(); - List supportedOutputFileExtensions = new ArrayList<>(); - - if (supportedFileExtensions != null) { - for (IFormat ext : supportedFileExtensions) { - if (ext.supportsParse()) { - supportedInputFileExtensions.add(ext.getFileExtension()); - } - if (ext.supportsWrite()) { - supportedOutputFileExtensions.add(ext.getFileExtension()); - } - } - } - - return Map.of( - "input", supportedInputFileExtensions, - "output", supportedOutputFileExtensions); - } - /** * Informs user about potential information loss occurring during file conversion * @param inputFileExtension file extension of the input file (lower case, no leading dot) From 7f04b54adce1bf3e2fd14f9c0c965e78d4893eeb Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 15:13:11 +0200 Subject: [PATCH 143/257] style: removed redundant null assignment --- .../java/de/featjar/feature/model/cli/FormatConversion.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 6d6bdfa7..6205be5f 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -339,7 +339,7 @@ private IFeatureModel inputParser(OptionList optionParser) { */ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { - IFormat format = null; + IFormat format; Optional> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() .filter(IFormat::supportsWrite) From 76288ef7be48164b8020f0bf368391d3b5e00697 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 10 Oct 2025 15:23:08 +0200 Subject: [PATCH 144/257] doc: added docs to unit tests --- .../model/cli/FormatConversionTest.java | 20 +++++++------------ 1 file changed, 7 insertions(+), 13 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index d59a8ac5..1dc536f7 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -71,8 +71,7 @@ void fileWritingTest() throws IOException { } /** - * - * + * Checks if appropriate error code is thrown when the input file does not exist. */ @Test void inputNotPresent() throws IOException { @@ -89,8 +88,7 @@ void inputNotPresent() throws IOException { } /** - * - * + * Checks if appropriate error code is thrown when the output file path has an invalid extension. */ @Test void invalidOutput() throws IOException { @@ -106,11 +104,10 @@ void invalidOutput() throws IOException { } /** - * - * + * Checks if appropriate error code is thrown when no model can be extracted from an input file. */ @Test - void modelNotParsed() throws IOException { + void modelNotParsed() { inputPath = "src/test/java/de/featjar/feature/model/cli/resources/emptyModel.xml"; outputPath = "model_modelNotParsed.xml"; @@ -120,19 +117,17 @@ void modelNotParsed() throws IOException { } /** - * - * + * Checks if appropriate error code is thrown when a file is already present at output path and no overwrite is specified */ @Test - void modelPresentNoOverwrite() throws IOException { - + void modelPresentNoOverwrite() { int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", inputPath); assertEquals(4, exit_code); } /** * - * checks if an info loss message is produced + * checks if appropriate information loss messages are being produced */ @Test void infoLossMapTestTriggers() throws IOException { @@ -168,7 +163,6 @@ void infoLossMapTestTriggers() throws IOException { /** * Tests whether the converter can do an XML -> XML round trip with a basic feature model. - * */ @Test void testWriteAndOverwrite() throws IOException { From 9a5d02db35bc5c28748ce3c775a585c1bf227959 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Fri, 10 Oct 2025 16:08:07 +0200 Subject: [PATCH 145/257] feat: added csvformat and printer. refactor: regarding yaml and json and general tree analysis. test: adjusted existing tests and added csv test --- build.gradle | 1 + .../feature/model/analysis/AnalysisTree.java | 123 +-------- .../analysis/visitor/AnalysisTreeVisitor.java | 3 +- .../visitor/AnalysisTreeVisitorCSV.java | 57 ++++ .../model/io/csv/CSVAnalysisFormat.java | 75 ++++++ .../model/io/json/JSONAnalysisFormat.java | 10 +- .../transformer/AnalysisTreeTransformer.java | 244 ++++++++++++++++++ .../model/io/yaml/YAMLAnalysisFormat.java | 10 +- .../feature/model/AnalysisTreeTest.java | 10 +- .../feature/model/io/CSVExportTest.java | 70 +++++ .../feature/model/io/JSONExportTest.java | 8 +- .../feature/model/io/YAMLExportTest.java | 6 +- 12 files changed, 489 insertions(+), 128 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java create mode 100644 src/main/java/de/featjar/feature/model/io/csv/CSVAnalysisFormat.java create mode 100644 src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java create mode 100644 src/test/java/de/featjar/feature/model/io/CSVExportTest.java diff --git a/build.gradle b/build.gradle index 6f4e9863..45fa0bb9 100644 --- a/build.gradle +++ b/build.gradle @@ -8,6 +8,7 @@ dependencies { api testFixtures('de.featjar:formula') implementation 'org.json:json:20240303' implementation 'org.yaml:snakeyaml:2.5' + implementation 'tools.jackson.dataformat:jackson-dataformat-csv:3.0.0' } license { diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index 088fee24..f19ebf44 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -21,12 +21,7 @@ package de.featjar.feature.model.analysis; import de.featjar.base.tree.structure.ATree; -import de.featjar.feature.model.io.json.JSONAnalysisFormat; -import java.math.BigDecimal; import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.Iterator; import java.util.List; import java.util.Objects; @@ -47,9 +42,12 @@ public AnalysisTree(String name, T value) { this.value = value; } - public AnalysisTree(String name, AnalysisTree... children) { - super(children.length); - if (children.length > 0) super.setChildren(Arrays.asList(children)); + public AnalysisTree(String name, AnalysisTree firstchild, AnalysisTree... children) { + super(children.length + 1); + ArrayList> allChildren = new ArrayList<>(); + allChildren.add(firstchild); + java.util.Collections.addAll(allChildren, children); + if (allChildren.size() > 0) super.setChildren(allChildren); this.name = name; } @@ -96,118 +94,13 @@ public int hashCodeNode() { return Objects.hash(this.getClass(), this.name, this.value); } - /** - * Static function to convert a nested HashMap having integers, floats, doubles and other recursively defined HashMaps as value - * to its AnalysisTree representation. - * - * @param hashMap data to convert - * @param name specifies the name of root - * @return returns a recursively built tree including its children - */ - public static AnalysisTree hashMapToTree(HashMap hashMap, String name) { - AnalysisTree root = new AnalysisTree<>(name, (Object) null); - for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { - String currentKey = iterator.next(); - if (hashMap.get(currentKey) instanceof Integer) { - root.addChild(new AnalysisTree<>(currentKey, (int) hashMap.get(currentKey))); - } else if (hashMap.get(currentKey) instanceof Float) { - root.addChild(new AnalysisTree<>(currentKey, (float) hashMap.get(currentKey))); - } else if (hashMap.get(currentKey) instanceof Double) { - root.addChild(new AnalysisTree<>(currentKey, (double) hashMap.get(currentKey))); - } else if (hashMap.get(currentKey) instanceof HashMap) { - root.addChild(hashMapToTree((HashMap) hashMap.get(currentKey), currentKey)); - } else { - // TODO Add handling for other types or errors if needed - } - } - return root; - } - - /** - * Static function to convert a nested hashmap being processed by {@link JSONAnalysisFormat} into its AnalysisTree representation. - * This function is not supposed to be called initially, otherwise a root node with - * name having the whole tree as single child is returned. - * - * @param hashMap data to convert - * @param name name of the root - * @return returns the recursively built tree including its children - */ - public static AnalysisTree hashMapListToTree(HashMap hashMap, String name) { - AnalysisTree root = new AnalysisTree<>(name, (Object) null); - for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { - String currentKey = iterator.next(); - if (hashMap.get(currentKey) instanceof HashMap) { - root.addChild(hashMapListToTree((HashMap) hashMap.get(currentKey), currentKey)); - } else if (hashMap.get(currentKey) instanceof ArrayList) { - ArrayList currentElement = (ArrayList) hashMap.get(currentKey); - if (currentElement.get(1).equals("class java.lang.Double")) { - BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); - root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.doubleValue())); - } else if (currentElement.get(1).equals("class java.lang.Integer")) { - root.addChild(new AnalysisTree<>(currentKey, (int) currentElement.get(2))); - } else if (currentElement.get(1).equals("class java.lang.Float")) { - BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); - root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.floatValue())); - } - } - } - return root; - } - - /** - * Static function to convert a nested hashmap being processed by {@link JSONAnalysisFormat} into its AnalysisTree representation. - * This function is specially suited to process a hashmap having only a single key in its first layer. - * - * @param hashMap data to convert - * @return returns the recursively built tree including its children, or an empty tree with an empty name in case of an error - */ - public static AnalysisTree hashMapListToTree(HashMap hashMap) { - if (hashMap.size() == 1) { - String key = hashMap.keySet().iterator().next(); - return hashMapListToTree((HashMap) hashMap.get(key), key); - } else { - return new AnalysisTree<>(""); - } - } - - public static AnalysisTree hashMapListYamlToTree(HashMap hashMap, String name) { - AnalysisTree root = new AnalysisTree<>(name, (Object) null); - for (Iterator iterator = hashMap.keySet().iterator(); iterator.hasNext(); ) { - String currentKey = iterator.next(); - if (hashMap.get(currentKey) instanceof HashMap) { - root.addChild(hashMapListYamlToTree((HashMap) hashMap.get(currentKey), currentKey)); - } else if (hashMap.get(currentKey) instanceof ArrayList) { - ArrayList currentElement = (ArrayList) hashMap.get(currentKey); - if (currentElement.get(1).equals("class java.lang.Double")) { - double currentDeccimal = (double) currentElement.get(2); - root.addChild(new AnalysisTree<>(currentKey, currentDeccimal)); - } else if (currentElement.get(1).equals("class java.lang.Integer")) { - root.addChild(new AnalysisTree<>(currentKey, (int) currentElement.get(2))); - } else if (currentElement.get(1).equals("class java.lang.Float")) { - double currentDouble = (double) currentElement.get(2); - float currentDeccimal = (float) currentDouble; - root.addChild(new AnalysisTree<>(currentKey, currentDeccimal)); - } - } - } - return root; - } - - public static AnalysisTree hashMapListYamlToTree(HashMap hashMap) { - if (hashMap.size() == 1) { - String key = hashMap.keySet().iterator().next(); - return hashMapListYamlToTree((HashMap) hashMap.get(key), key); - } else { - return new AnalysisTree<>(""); - } - } - @Override public String toString() { if (this.value == null) { return "Name: " + this.name + " - Value: " + this.value + " - Value class: " + "No class"; } else { - return "Name: " + this.name + " - Value: " + this.value + " - Value class: " + this.value.getClass(); + return "Name: " + this.name + " - Value: " + this.value + " - Value class: " + + this.value.getClass().getName(); } } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java index 581d4a04..40e8d1ca 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java @@ -22,7 +22,6 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.visitor.ITreeVisitor; -import de.featjar.base.tree.visitor.ITreeVisitor.TraversalAction; import de.featjar.feature.model.analysis.AnalysisTree; import java.util.ArrayList; import java.util.Arrays; @@ -64,7 +63,7 @@ public TraversalAction firstVisit(List> path) { currentMap.put( node.getName(), new ArrayList(Arrays.asList( - node.getName(), node.getValue().getClass().toString(), node.getValue()))); + node.getName(), node.getValue().getClass().getName(), node.getValue()))); } else { currentMap.put(node.getName(), new HashMap()); } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java new file mode 100644 index 00000000..733b1955 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis.visitor; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.feature.model.analysis.AnalysisTree; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class AnalysisTreeVisitorCSV implements ITreeVisitor, ArrayList> { + ArrayList nodesList = new ArrayList(); + + @Override + public TraversalAction firstVisit(List> path) { + final AnalysisTree node = ITreeVisitor.getCurrentNode(path); + if (node.getChildrenCount() == 0 && ITreeVisitor.getParentNode(path).isPresent()) { + final AnalysisTree parent = ITreeVisitor.getParentNode(path).get(); + nodesList.add(Arrays.asList( + parent.getName(), + node.getName(), + node.getValue(), + node.getValue().getClass().getName())); + } + return TraversalAction.CONTINUE; + } + + @Override + public void reset() { + nodesList = new ArrayList(); + } + + @Override + public Result> getResult() { + + return Result.of(nodesList); + } +} diff --git a/src/main/java/de/featjar/feature/model/io/csv/CSVAnalysisFormat.java b/src/main/java/de/featjar/feature/model/io/csv/CSVAnalysisFormat.java new file mode 100644 index 00000000..f9257296 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/io/csv/CSVAnalysisFormat.java @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.io.csv; + +import de.featjar.base.data.Result; +import de.featjar.base.io.format.IFormat; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitorCSV; +import java.util.ArrayList; +import java.util.Arrays; +import tools.jackson.dataformat.csv.CsvMapper; +import tools.jackson.dataformat.csv.CsvSchema; + +/** + * An IFormat class that take an AnalysisTree as input and can serialize it into CSV String. + * With the CSV having only four columns (AnalysisType, Name, Value, Class) + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ +public class CSVAnalysisFormat implements IFormat> { + + @Override + public String getName() { + return "CSV"; + } + + @Override + public String getFileExtension() { + return "csv"; + } + + @Override + public boolean supportsParse() { + return false; + } + + @Override + public boolean supportsWrite() { + return true; + } + + @Override + public Result serialize(AnalysisTree analysisTree) { + CsvMapper CSVMapper = new CsvMapper(); + ArrayList nodesList = new ArrayList(); + nodesList.add(Arrays.asList("AnalysisType", "Name", "Value", "Class")); + CsvSchema schema = CsvSchema.emptySchema() + .withColumnSeparator(';') + .withLineSeparator("\n") + .withoutQuoteChar(); + nodesList.addAll( + Trees.traverse(analysisTree, new AnalysisTreeVisitorCSV()).get()); + return Result.of(CSVMapper.writer(schema).writeValueAsString(nodesList)); + } +} diff --git a/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java index 36b5647a..69b70450 100644 --- a/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java +++ b/src/main/java/de/featjar/feature/model/io/json/JSONAnalysisFormat.java @@ -26,12 +26,16 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import java.util.HashMap; import org.json.JSONObject; /** - * An IFormat class that take an AnalysisTree as input and can serialize it into JSON String - * and from JSON String + * An IFormat class that takes an AnalysisTree as input and can serialize it into JSON String + * and from JSON String. For the exact build of the JSON String please look in {@link AnalysisTreeVisitor}. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese */ public class JSONAnalysisFormat implements IFormat> { @@ -66,6 +70,6 @@ public Result serialize(AnalysisTree analysisTree) { public Result> parse(AInputMapper inputMapper) { HashMap jsonMap = (HashMap) new JSONObject(inputMapper.get().text()).toMap(); - return Result.of(AnalysisTree.hashMapListToTree(jsonMap)); + return AnalysisTreeTransformer.jsonHashMapToTree(jsonMap); } } diff --git a/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java new file mode 100644 index 00000000..e242a628 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.io.transformer; + +import de.featjar.base.FeatJAR; +import de.featjar.base.data.Result; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.io.json.JSONAnalysisFormat; +import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; +import java.math.BigDecimal; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map.Entry; + +/** + * A class that handles transformations into AnalysisTree. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ +public class AnalysisTreeTransformer { + + /** + * Static function to convert a nested HashMap having integers, floats, doubles and other recursively defined HashMaps as value + * to its AnalysisTree representation. + * + * @param hashMap data to convert + * @param name specifies the name of root + * @return on success returns a recursively built tree including its children wrapped in a Result<>. + * On failure return an empty Result + */ + public static Result> hashMapToTree(HashMap hashMap, String name) { + AnalysisTree root = new AnalysisTree<>(name, (Object) null); + for (Iterator> iterator = hashMap.entrySet().iterator(); iterator.hasNext(); ) { + Entry currentEntry = iterator.next(); + String currentKey = currentEntry.getKey(); + Object currentValue = currentEntry.getValue(); + if (currentValue instanceof Integer) { + root.addChild(new AnalysisTree<>(currentKey, (int) currentValue)); + } else if (currentValue instanceof Float) { + root.addChild(new AnalysisTree<>(currentKey, (float) currentValue)); + } else if (currentValue instanceof Double) { + root.addChild(new AnalysisTree<>(currentKey, (double) currentValue)); + } else if (currentValue instanceof HashMap) { + Result> result = hashMapToTree((HashMap) currentValue, currentKey); + if (result.isPresent()) { + root.addChild(result.get()); + } else { + return Result.empty(); + } + + } else { + FeatJAR.log() + .error("An innermost element of the Map data structure was not of type " + + "Float, Double, Integer, or HashMap"); + return Result.empty(); + } + } + return Result.of(root); + } + + /** + * Static function to convert a nested HashMap being processed by {@link JSONAnalysisFormat} into its AnalysisTree representation. + * This function is not supposed to be called initially, otherwise a root node with + * name having the whole tree as single child is returned. + * + * @param hashMap data to convert + * @param name name of the root + * @return on success returns a recursively built tree including its children wrapped in a Result<>. + * On failure return an empty Result + */ + public static Result> jsonHashMapToTree(HashMap hashMap, String name) { + AnalysisTree root = new AnalysisTree<>(name, (Object) null); + for (Iterator> iterator = hashMap.entrySet().iterator(); iterator.hasNext(); ) { + Entry currentEntry = iterator.next(); + String currentKey = currentEntry.getKey(); + Object currentValue = currentEntry.getValue(); + if (currentValue instanceof HashMap) { + Result> result = jsonHashMapToTree((HashMap) currentValue, currentKey); + if (result.isPresent()) { + root.addChild(result.get()); + } else { + return result; + } + } else if (currentValue instanceof ArrayList) { + ArrayList currentElement = (ArrayList) currentValue; + if (!(currentElement.size() == 3)) { + FeatJAR.log() + .error("An innermost element of the Map/YAML data structure does not contain " + + "exactly three elements"); + return Result.empty(); + } + if (!(currentElement.get(0) instanceof String)) { + FeatJAR.log() + .error("The first element of an innermost element of the Map/YAML data structure " + + "was not from the type String"); + return Result.empty(); + } + if (!(currentElement.get(1) instanceof String)) { + FeatJAR.log() + .error("The second element of an innermost element of the Map/YAML data structure " + + "was not from the type String"); + return Result.empty(); + } + if (!(currentElement.get(2) instanceof BigDecimal || currentElement.get(2) instanceof Integer)) { + FeatJAR.log() + .error("The third element of an innermost element of the Map/YAML data structure " + + "was not from the type String"); + return Result.empty(); + } + + if (currentElement.get(1).equals("java.lang.Double")) { + BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.doubleValue())); + } else if (currentElement.get(1).equals("java.lang.Integer")) { + root.addChild(new AnalysisTree<>(currentKey, (int) currentElement.get(2))); + } else if (currentElement.get(1).equals("java.lang.Float")) { + BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.floatValue())); + } + } + } + return Result.of(root); + } + + /** + * Static function to convert a nested HashMap being processed by {@link JSONAnalysisFormat} into its AnalysisTree representation. + * This function is specially suited to process a HashMap having only a single key in its first layer. + * + * @param hashMap data to convert + * @return on success returns a recursively built tree including its children wrapped in a Result<>. + * On failure return an empty Result + */ + public static Result> jsonHashMapToTree(HashMap hashMap) { + if (hashMap.size() == 1) { + Entry currentEntry = hashMap.entrySet().iterator().next(); + return jsonHashMapToTree((HashMap) currentEntry.getValue(), currentEntry.getKey()); + } else { + return Result.empty(); + } + } + + /** + * Static function to convert a nested HashMap being processed by {@link YAMLAnalysisFormat} into its AnalysisTree representation. + * This function is not supposed to be called initially, otherwise a root node with + * name having the whole tree as single child is returned. + * + * @param hashMap data to convert + * @return on success returns a recursively built tree including its children wrapped in a Result<>. + * On failure return an empty Result + */ + public static Result> yamlHashMapToTree(HashMap hashMap, String name) { + AnalysisTree root = new AnalysisTree<>(name, (Object) null); + for (Iterator> iterator = hashMap.entrySet().iterator(); iterator.hasNext(); ) { + Entry currentEntry = iterator.next(); + String currentKey = currentEntry.getKey(); + Object currentValue = currentEntry.getValue(); + if (currentValue instanceof HashMap) { + Result> result = yamlHashMapToTree((HashMap) currentValue, currentKey); + if (result.isPresent()) { + root.addChild(result.get()); + } else { + return result; + } + } else if (currentValue instanceof ArrayList) { + ArrayList currentElement = (ArrayList) currentValue; + if (!(currentElement.size() == 3)) { + FeatJAR.log() + .error("An innermost element of the Map/YAML data structure does not contain " + + "exactly three elements"); + return Result.empty(); + } + if (!(currentElement.get(0) instanceof String)) { + FeatJAR.log() + .error("The first element of an innermost element of the Map/YAML data structure " + + "was not from the type String"); + return Result.empty(); + } + if (!(currentElement.get(1) instanceof String)) { + FeatJAR.log() + .error("The second element of an innermost element of the Map/YAML data structure " + + "was not from the type String"); + return Result.empty(); + } + if (!(currentElement.get(2) instanceof Double || currentElement.get(2) instanceof Integer)) { + System.out.println(currentElement.get(2).getClass()); + FeatJAR.log() + .error("The third element of an innermost element of the Map/YAML data structure " + + "was not from the type String"); + return Result.empty(); + } + Object typeString = currentElement.get(1); + if (typeString.equals("java.lang.Double")) { + double currentDeccimal = (double) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal)); + } else if (typeString.equals("java.lang.Integer")) { + root.addChild(new AnalysisTree<>(currentKey, (int) currentElement.get(2))); + } else if ("java.lang.Float".equals(typeString)) { + double currentDouble = (double) currentElement.get(2); + float currentDeccimal = (float) currentDouble; + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal)); + } + } + } + return Result.of(root); + } + + /** + * Static function to convert a nested HashMap being processed by {@link YAMLAnalysisFormat} into its AnalysisTree representation. + * This function is only suited to process a HashMap having only a single key in its first layer. Otherwise + * an empty result will be returned. + * + * @param hashMap data to convert + * @return on success returns a recursively built tree including its children wrapped in a Result<>. + * On failure return an empty Result + */ + public static Result> yamlHashMapToTree(HashMap hashMap) { + if (hashMap.size() == 1) { + Entry currentEntry = hashMap.entrySet().iterator().next(); + return yamlHashMapToTree((HashMap) currentEntry.getValue(), currentEntry.getKey()); + } else { + return Result.empty(); + } + } +} diff --git a/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java b/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java index 18864392..7eaa1323 100644 --- a/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java +++ b/src/main/java/de/featjar/feature/model/io/yaml/YAMLAnalysisFormat.java @@ -26,9 +26,17 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import java.util.HashMap; import org.yaml.snakeyaml.Yaml; +/** + * An IFormat class that take an AnalysisTree as input and can serialize it into YAML String + * and from YAML String. For the exact build of the YAML String please look in {@link AnalysisTreeVisitor}. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class YAMLAnalysisFormat implements IFormat> { @Override @@ -63,6 +71,6 @@ public Result> parse(AInputMapper inputMapper) { Yaml yaml = new Yaml(); HashMap yamlHashMap = (HashMap) yaml.load(inputMapper.get().text()); - return Result.of(AnalysisTree.hashMapListYamlToTree(yamlHashMap)); + return AnalysisTreeTransformer.yamlHashMapToTree(yamlHashMap); } } diff --git a/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java b/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java index d93c1fb2..5abe0fef 100644 --- a/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java +++ b/src/test/java/de/featjar/feature/model/AnalysisTreeTest.java @@ -26,6 +26,7 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; @@ -34,7 +35,8 @@ public class AnalysisTreeTest { @Test public void mapToTreeTest() { LinkedHashMap emptyMap = new LinkedHashMap(); - AnalysisTree returnedTree = AnalysisTree.hashMapToTree(emptyMap, "empty"); + AnalysisTree returnedTree = + AnalysisTreeTransformer.hashMapToTree(emptyMap, "empty").get(); assertEquals(returnedTree.getName(), "empty"); assertEquals(returnedTree.getChildrenCount(), 0); @@ -44,7 +46,8 @@ public void mapToTreeTest() { emptyMap.put("floatfirstLevel", (float) 42); emptyMap.put("doublefirstLevel", (double) 42); - returnedTree = AnalysisTree.hashMapToTree(emptyMap, "valuesFirstLevel"); + returnedTree = AnalysisTreeTransformer.hashMapToTree(emptyMap, "valuesFirstLevel") + .get(); assertEquals(returnedTree.getChildrenCount(), 3); assertTrue(returnedTree.getChild(0).isPresent()); assertEquals(returnedTree.getChild(0).get().getName(), "intfirstLevel"); @@ -69,7 +72,8 @@ public void mapToTreeTest() { firstLevelMap.put("map2secondLevel", secondLevelMap2); emptyMap.put("mapfirstLevel", firstLevelMap); - returnedTree = AnalysisTree.hashMapToTree(emptyMap, "nestedMaps"); + returnedTree = + AnalysisTreeTransformer.hashMapToTree(emptyMap, "nestedMaps").get(); assertEquals(returnedTree.getChildrenCount(), 4); AnalysisTree mapfirstLevel = returnedTree.getChild(3).get(); diff --git a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java new file mode 100644 index 00000000..c8c3c6c0 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java @@ -0,0 +1,70 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.io; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.io.csv.CSVAnalysisFormat; +import java.io.IOException; +import org.junit.jupiter.api.Test; + +public class CSVExportTest { + + public AnalysisTree createDefaultTree() { + AnalysisTree innereanalysisTree = new AnalysisTree<>( + "avgNumOfAtomsPerConstraints", + new AnalysisTree<>("xo", 3.3), + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); + + AnalysisTree analysisTree = new AnalysisTree<>( + "Analysis", + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), + new AnalysisTree<>("numOfTopFeatures", 3.3), + new AnalysisTree<>("treeDepth", 3), + new AnalysisTree<>("avgNumOfChildren", 3), + new AnalysisTree<>("numInOrGroups", 7), + new AnalysisTree<>("numInAltGroups", 5), + new AnalysisTree<>("numOfAtoms", 8), + new AnalysisTree<>("avgNumOfAsss", 4), + innereanalysisTree); + return analysisTree; + } + + @Test + public void CSVTest() throws IOException { + CSVAnalysisFormat csvAnalysisFormat = new CSVAnalysisFormat(); + String csvString = csvAnalysisFormat.serialize(createDefaultTree()).orElseThrow(); + assertEquals( + csvString, + "AnalysisType;Name;Value;Class\n" + + "Analysis;numOfLeafFeatures;12.4;java.lang.Float\n" + + "Analysis;numOfTopFeatures;3.3;java.lang.Double\n" + + "Analysis;treeDepth;3;java.lang.Integer\n" + + "Analysis;avgNumOfChildren;3;java.lang.Integer\n" + + "Analysis;numInOrGroups;7;java.lang.Integer\n" + + "Analysis;numInAltGroups;5;java.lang.Integer\n" + + "Analysis;numOfAtoms;8;java.lang.Integer\n" + + "Analysis;avgNumOfAsss;4;java.lang.Integer\n" + + "avgNumOfAtomsPerConstraints;xo;3.3;java.lang.Double\n" + + "avgNumOfAtomsPerConstraints;numOfLeafFeatures;12.4;java.lang.Float\n"); + } +} diff --git a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java index fcec4c07..8d768877 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java @@ -22,10 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertTrue; +import de.featjar.base.data.Result; import de.featjar.base.io.IO; import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.io.json.JSONAnalysisFormat; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import java.io.IOException; import java.nio.file.Paths; import java.util.HashMap; @@ -72,14 +74,16 @@ public void JSONSerialize() throws IOException { data.put("numOfAtoms", 8); data.put("avgNumOfAsss", 4); - AnalysisTree analsyisTree = AnalysisTree.hashMapToTree(data, "Analysis"); + Result> analsyisTreeResult = AnalysisTreeTransformer.hashMapToTree(data, "Analysis"); + AnalysisTree analsyisTree = analsyisTreeResult.get(); JSONAnalysisFormat jsonFormat = new JSONAnalysisFormat(); JSONObject firstJSONObject = new JSONObject(jsonFormat.serialize(analsyisTree).get()); String jsonString = firstJSONObject.toString(); JSONObject secondJSONJsonObject = new JSONObject(jsonString); HashMap jsonAsMap = (HashMap) secondJSONJsonObject.toMap(); - AnalysisTree analsyisTreeAfterConversion = AnalysisTree.hashMapListToTree(jsonAsMap); + AnalysisTree analsyisTreeAfterConversion = + AnalysisTreeTransformer.jsonHashMapToTree(jsonAsMap).get(); analsyisTree.sort(); analsyisTreeAfterConversion.sort(); diff --git a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java index e87fcfc2..c96e9071 100644 --- a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java @@ -25,6 +25,7 @@ import de.featjar.base.io.IO; import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; import java.io.IOException; import java.nio.file.Paths; @@ -72,7 +73,7 @@ public void YAMLTest() throws IOException { } @Test - public void JSONSerialize() throws IOException { + public void YAMLSerialize() throws IOException { LinkedHashMap innerMap = new LinkedHashMap(); innerMap.put("xo", 3.3); innerMap.put("numOfLeafFeatures", (float) 12.4); @@ -97,7 +98,8 @@ public void JSONSerialize() throws IOException { Map map = (Map) loadedObject; HashMap loadedHashMap = (HashMap) map; - AnalysisTree analsyisTreeAfterConversion = AnalysisTree.hashMapListYamlToTree(loadedHashMap); + AnalysisTree analsyisTreeAfterConversion = + AnalysisTreeTransformer.yamlHashMapToTree(loadedHashMap).get(); analsyisTree.sort(); analsyisTreeAfterConversion.sort(); From fdd5e9cc3dfe7aaa1539f6796b9ca28816fd9594 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 16:42:41 +0200 Subject: [PATCH 146/257] style: fix imports and delete debug statements --- .../featjar/feature/model/cli/FormatConversion.java | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index 6205be5f..f54ea31b 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -32,7 +32,15 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.util.*; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.EnumMap; +import java.util.HashMap; + + + import java.util.stream.Collectors; /** @@ -130,7 +138,6 @@ public String toString() { public int run(OptionList optionParser) { if (!checkIfInputOutputIsPresent(optionParser)) { - System.out.println("HERE"); return 1; } Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); @@ -298,11 +305,9 @@ private boolean checkIfFileExtensionsValid(String inputFileExtension, String out */ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { if (!optionParser.getResult(INPUT_OPTION).isPresent()) { - System.out.println("HERE1"); FeatJAR.log().error("No input path provided."); return false; } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { - System.out.println("HERE2"); FeatJAR.log().error("No output path provided."); return false; } From 3a60cf8ed82aff070af536e2cc068eb7492238ed Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 10 Oct 2025 16:45:18 +0200 Subject: [PATCH 147/257] style: fix more imports and delete debug statements --- src/main/java/de/featjar/feature/model/cli/PrintStatistics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index c4500ce4..f10776be 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -237,7 +237,7 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) for (Map.Entry entry : data.entrySet()) { if (entry.getKey().equals("Number of Atoms")) { - outputString.append(String.format("\n\t\t%-40s %n", "CONSTRAINT RELATED STATS\n")); + outputString.append(String.format("\n %-40s %n", "CONSTRAINT RELATED STATS\n")); } else if (entry.getKey().equals("[Tree 1] Average Number of Children")) { outputString.append(String.format("\n\t\t%-40s %n", "TREE RELATED STATS\n")); From 8a2c654e3cc0878865d9c6a5a1f256914e7638fc Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Mon, 13 Oct 2025 11:10:49 +0200 Subject: [PATCH 148/257] refactor: improved functionality for logging across different operating systems. --- .../feature/model/cli/FormatConversion.java | 49 ++++++++++--------- .../feature/model/cli/PrintStatistics.java | 6 +-- .../model/cli/FormatConversionTest.java | 8 +-- .../model/cli/PrintStatisticsTest.java | 11 +++-- 4 files changed, 40 insertions(+), 34 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index f54ea31b..06b90b82 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -32,15 +32,12 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.EnumMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.EnumMap; -import java.util.HashMap; - - - import java.util.stream.Collectors; /** @@ -50,17 +47,17 @@ */ public class FormatConversion implements ICommand { - private static final List supportedInputFileExtensions = FeatureModelFormats.getInstance().getExtensions() - .stream() - .filter(IFormat::supportsParse) - .map(IFormat::getFileExtension) - .collect(Collectors.toList()); + private static final List supportedInputFileExtensions = + FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsParse) + .map(IFormat::getFileExtension) + .collect(Collectors.toList()); - private static final List supportedOutputFileExtensions = FeatureModelFormats.getInstance().getExtensions() - .stream() - .filter(IFormat::supportsWrite) - .map(IFormat::getFileExtension) - .collect(Collectors.toList()); + private static final List supportedOutputFileExtensions = + FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .map(IFormat::getFileExtension) + .collect(Collectors.toList()); public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) @@ -171,8 +168,9 @@ public int run(OptionList optionParser) { */ private void infoLossMessage(String inputFileExtension, String outputFileExtension) { - StringBuilder msg = new StringBuilder( - "Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n"); + StringBuilder msg = new StringBuilder(); + msg.append("Info Loss:\n"); + Map> infoLossMap = buildInfoLossMap(); Map iSupports = infoLossMap.get(inputFileExtension); @@ -181,17 +179,22 @@ private void infoLossMessage(String inputFileExtension, String outputFileExtensi if (iSupports == null || oSupports == null) { return; } - + boolean infoLossPresent = false; for (FileInfo fileInfo : iSupports.keySet()) { SupportLevel iSupportLevel = iSupports.get(fileInfo); SupportLevel oSupportLevel = oSupports.get(fileInfo); if (oSupportLevel.isLessThan(iSupportLevel)) { - msg.append("\t" + fileInfo + " \t\t" + iSupportLevel + "\t" + oSupportLevel + "\n"); + if (!infoLossPresent) { + msg.append(String.format( + "%-46s %s%n", "", inputFileExtension + " --> " + outputFileExtension + "\n")); + infoLossPresent = true; + } + + msg.append(String.format("%-36s %14s %5s%n", " " + fileInfo, iSupportLevel, oSupportLevel)); } } - if (!msg.toString() - .equals("Info Loss:" + "\n\t\t\t\t\t\t" + inputFileExtension + " --> " + outputFileExtension + "\n")) { + if (infoLossPresent) { FeatJAR.log().warning(msg.toString()); } else { FeatJAR.log().info("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); @@ -360,11 +363,11 @@ public int saveFile(Path outputPath, IFeatureModel model, String outputFileExten try { if (Files.exists(outputPath)) { if (overWriteOutputFile) { - FeatJAR.log().info("File already present at: " + outputPath + "\n\tContinuing to overwrite File."); + FeatJAR.log().info("File already present at: " + outputPath + ". Continuing to overwrite File."); } else { FeatJAR.log() .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath - + "\n\tTo overwrite present file add --overwrite"); + + ". To overwrite present file add --overwrite"); return 4; } } diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index f10776be..af5fdaa9 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -240,7 +240,7 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) outputString.append(String.format("\n %-40s %n", "CONSTRAINT RELATED STATS\n")); } else if (entry.getKey().equals("[Tree 1] Average Number of Children")) { - outputString.append(String.format("\n\t\t%-40s %n", "TREE RELATED STATS\n")); + outputString.append(String.format("\n %-40s %n", "TREE RELATED STATS\n")); } if (entry.getValue() instanceof Map) { Map nestedMap = (Map) entry.getValue(); @@ -248,8 +248,8 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) outputString.append(String.format("%-40s%n", entry.getKey())); for (Map.Entry nestedEntry : nestedMap.entrySet()) { - outputString.append( - String.format("%-33s : %s%n", "\t " + nestedEntry.getKey(), nestedEntry.getValue())); + outputString.append(String.format( + "%-40s : %s%n", " " + nestedEntry.getKey(), nestedEntry.getValue())); } } else { outputString.append(String.format("%-40s : %s%n", entry.getKey(), entry.getValue())); diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 1dc536f7..7f444979 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -150,10 +150,12 @@ void infoLossMapTestTriggers() throws IOException { String string = new String(byteArray); String expected_output = "Info Loss:\n" - + " xml --> dot\n" - + " File can be used for input YES NO\n" + + " xml --> dot\n" + "\n" - + "Output model saved at: model_invalidInput.dot\n"; + + " File can be used for input YES NO\n" + + "\n" + + "Output model saved at: model_invalidInput.dot\n" + + ""; assertEquals(expected_output, string); assertTrue(string.startsWith(expected_output)); diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 45e987ed..a95a4501 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -141,16 +141,17 @@ void prettyStringBuilder() { String comparison = "Normal Entry : 10\n" + "HashMap Entry \n" - + " Nested Entry 1 : 5\n" - + " Nested Entry 2 : 6\n" + + " Nested Entry 1 : 5\n" + + " Nested Entry 2 : 6\n" + "\n" - + " CONSTRAINT RELATED STATS\n" + + " CONSTRAINT RELATED STATS\n" + " \n" + "Number of Atoms : \n" + "\n" - + " TREE RELATED STATS\n" + + " TREE RELATED STATS\n" + " \n" - + "[Tree 1] Average Number of Children : \n"; + + "[Tree 1] Average Number of Children : \n" + + ""; assertEquals(comparison, printStats.buildStringPrettyStats(testData).toString()); } From f69e4024a197324a9413c5c7ddf9c48873eebe2e Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Mon, 13 Oct 2025 11:13:18 +0200 Subject: [PATCH 149/257] docs: added missing doc. style: renamed variables --- .../visitor/AnalysisTreeVisitorCSV.java | 12 +++++++---- .../model/io/csv/CSVAnalysisFormat.java | 2 +- .../feature/model/io/JSONExportTest.java | 8 ++++---- .../feature/model/io/YAMLExportTest.java | 20 +++++++++---------- 4 files changed, 23 insertions(+), 19 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java index 733b1955..21817208 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java @@ -27,6 +27,13 @@ import java.util.Arrays; import java.util.List; +/** + * Enumerates all leafs containing data in order to be processed by CSV formats. + * Each leaf is represented by a list containing parent name, its name, value, type. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class AnalysisTreeVisitorCSV implements ITreeVisitor, ArrayList> { ArrayList nodesList = new ArrayList(); @@ -36,10 +43,7 @@ public TraversalAction firstVisit(List> path) { if (node.getChildrenCount() == 0 && ITreeVisitor.getParentNode(path).isPresent()) { final AnalysisTree parent = ITreeVisitor.getParentNode(path).get(); nodesList.add(Arrays.asList( - parent.getName(), - node.getName(), - node.getValue(), - node.getValue().getClass().getName())); + parent.getName(), node.getName(), node.getValue().getClass().getName(), node.getValue())); } return TraversalAction.CONTINUE; } diff --git a/src/main/java/de/featjar/feature/model/io/csv/CSVAnalysisFormat.java b/src/main/java/de/featjar/feature/model/io/csv/CSVAnalysisFormat.java index f9257296..b4729231 100644 --- a/src/main/java/de/featjar/feature/model/io/csv/CSVAnalysisFormat.java +++ b/src/main/java/de/featjar/feature/model/io/csv/CSVAnalysisFormat.java @@ -63,7 +63,7 @@ public boolean supportsWrite() { public Result serialize(AnalysisTree analysisTree) { CsvMapper CSVMapper = new CsvMapper(); ArrayList nodesList = new ArrayList(); - nodesList.add(Arrays.asList("AnalysisType", "Name", "Value", "Class")); + nodesList.add(Arrays.asList("AnalysisType", "Name", "Class", "Value")); CsvSchema schema = CsvSchema.emptySchema() .withColumnSeparator(';') .withLineSeparator("\n") diff --git a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java index 8d768877..5ba6a343 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java @@ -42,7 +42,7 @@ public class JSONExportTest { public AnalysisTree createDefaultTree() { AnalysisTree innereanalysisTree = new AnalysisTree<>( "avgNumOfAtomsPerConstraints", - new AnalysisTree<>("xo", 3.3), + new AnalysisTree<>("test property", 3.3), new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); AnalysisTree analysisTree = new AnalysisTree<>( @@ -62,7 +62,7 @@ public AnalysisTree createDefaultTree() { @Test public void JSONSerialize() throws IOException { LinkedHashMap innerMap = new LinkedHashMap(); - innerMap.put("xo", 3.3); + innerMap.put("test property", 3.3); innerMap.put("numOfLeafFeatures", (float) 12.4); data.put("numOfTopFeatures", 3.3); data.put("numOfLeafFeatures", (float) 12.4); @@ -80,8 +80,8 @@ public void JSONSerialize() throws IOException { JSONObject firstJSONObject = new JSONObject(jsonFormat.serialize(analsyisTree).get()); String jsonString = firstJSONObject.toString(); - JSONObject secondJSONJsonObject = new JSONObject(jsonString); - HashMap jsonAsMap = (HashMap) secondJSONJsonObject.toMap(); + JSONObject secondJSONObject = new JSONObject(jsonString); + HashMap jsonAsMap = (HashMap) secondJSONObject.toMap(); AnalysisTree analsyisTreeAfterConversion = AnalysisTreeTransformer.jsonHashMapToTree(jsonAsMap).get(); diff --git a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java index c96e9071..866d760d 100644 --- a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java @@ -40,9 +40,9 @@ public class YAMLExportTest { LinkedHashMap data = new LinkedHashMap(); public AnalysisTree createDefaultTree() { - AnalysisTree innereanalysisTree = new AnalysisTree<>( + AnalysisTree innereAnalysisTree = new AnalysisTree<>( "avgNumOfAtomsPerConstraints", - new AnalysisTree<>("xo", 3.3), + new AnalysisTree<>("test property", 3.3), new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); AnalysisTree analysisTree = new AnalysisTree<>( @@ -55,7 +55,7 @@ public AnalysisTree createDefaultTree() { new AnalysisTree<>("numInAltGroups", 5), new AnalysisTree<>("numOfAtoms", 8), new AnalysisTree<>("avgNumOfAsss", 4), - innereanalysisTree); + innereAnalysisTree); return analysisTree; } @@ -75,7 +75,7 @@ public void YAMLTest() throws IOException { @Test public void YAMLSerialize() throws IOException { LinkedHashMap innerMap = new LinkedHashMap(); - innerMap.put("xo", 3.3); + innerMap.put("test property", 3.3); innerMap.put("numOfLeafFeatures", (float) 12.4); data.put("numOfTopFeatures", 3.3); data.put("numOfLeafFeatures", (float) 12.4); @@ -98,18 +98,18 @@ public void YAMLSerialize() throws IOException { Map map = (Map) loadedObject; HashMap loadedHashMap = (HashMap) map; - AnalysisTree analsyisTreeAfterConversion = + AnalysisTree analysisTreeAfterConversion = AnalysisTreeTransformer.yamlHashMapToTree(loadedHashMap).get(); analsyisTree.sort(); - analsyisTreeAfterConversion.sort(); + analysisTreeAfterConversion.sort(); assertTrue( - Trees.equals(analsyisTree, analsyisTreeAfterConversion), - "firstTree\n" + analsyisTree.print() + "\nsecond tree\n" + analsyisTreeAfterConversion.print()); + Trees.equals(analsyisTree, analysisTreeAfterConversion), + "firstTree\n" + analsyisTree.print() + "\nsecond tree\n" + analysisTreeAfterConversion.print()); AnalysisTree manualAnalysisTree = createDefaultTree(); manualAnalysisTree.sort(); assertTrue( - Trees.equals(manualAnalysisTree, analsyisTreeAfterConversion), - "firstTree\n" + manualAnalysisTree.print() + "\nsecond tree\n" + analsyisTreeAfterConversion.print()); + Trees.equals(manualAnalysisTree, analysisTreeAfterConversion), + "firstTree\n" + manualAnalysisTree.print() + "\nsecond tree\n" + analysisTreeAfterConversion.print()); } } From 8b87d98be843ea8ac4e1192b05c5f0ed9af3259d Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Mon, 13 Oct 2025 11:59:54 +0200 Subject: [PATCH 150/257] feat: created files for next story card --- .../cli/ConfigurationFormatConversion.java | 406 ++++++++++++++++++ .../feature/model/cli/PrintStatistics.java | 8 +- src/main/resources/extensions.xml | 1 + 3 files changed, 413 insertions(+), 2 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java new file mode 100644 index 00000000..8d87171e --- /dev/null +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.cli; + +import de.featjar.base.FeatJAR; +import de.featjar.base.cli.ICommand; +import de.featjar.base.cli.Option; +import de.featjar.base.cli.OptionList; +import de.featjar.base.data.Result; +import de.featjar.base.io.IO; +import de.featjar.base.io.format.IFormat; +import de.featjar.feature.model.IFeatureModel; +import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.formula.assignment.BooleanAssignmentValueMap; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.EnumMap; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Prints statistics about a provided Feature Model. + * + * @author Knut, Kilian & Benjamin + */ + +// BooleanAssignmentValueMapFormat implements IFormat + +public class ConfigurationFormatConversion implements ICommand { + + private static final List supportedInputFileExtensions = + FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsParse) + .map(IFormat::getFileExtension) + .collect(Collectors.toList()); + + private static final List supportedOutputFileExtensions = + FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .map(IFormat::getFileExtension) + .collect(Collectors.toList()); + + public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) + .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) + .setValidator(Option.PathValidator); + + public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) + .setDescription("Path to output file. Accepted File Types: " + supportedOutputFileExtensions); + + public static final Option OVERWRITE = + Option.newFlag("overwrite").setDescription("Overwrite output file."); + + /** + * @return all options registered for the calling class. + */ + public final List> getOptions() { + return Option.getAllOptions(getClass()); + } + + /** + * For info loss map; indicates whether a feature is supported fully, partially, or not at all. + */ + private enum SupportLevel { + NO(0), + YES(1); + + public final int rank; + + SupportLevel(int rank) { + this.rank = rank; + } + + boolean isLessThan(SupportLevel other) { + return this.rank < other.rank; + } + } + + /** + * For info loss map. + * Saving name as well as a description in case we need to explain it to the user later. + */ + private enum FileInfo { + basicHierarchy("General hierarchical Structure"), + subgroupHierarchy("Hierarchy with subgroups"), + featureDescription("Features with descriptions"), + featureAttributes("Features with attributes"), + featureCardinality("Cardinality of features"), + booleanOperators("Features of boolean operators"), + allOperators("Features of all operators"), + parseable("File can be used for input"); + + public final String name; + + FileInfo(String name) { + this.name = name; + } + + @Override + public String toString() { + return name; + } + } + + /** + * main function for handling format conversion + * @param optionParser supplied by command line execution. + * + * @return 0 on success + * 1 if output/input aren't present + * 2 if input/output file type is invalid + * 3 if the model could not be parsed, + * 4 if a file is already present at output path and no overwrite is specified + * 5 on IOException + */ + @Override + public int run(OptionList optionParser) { + + if (!checkIfInputOutputIsPresent(optionParser)) { + return 1; + } + Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); + + // check if provided file extensions are supported + String inputFileExtension = + IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get()); + String outputFileExtension = + IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); + if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { + return 2; + } + + // informing user about information loss during conversion between file formats + infoLossMessage(inputFileExtension, outputFileExtension); + + // check if model was corrected extracted from input + IFeatureModel model = inputParser(optionParser); + if (model == null) { + FeatJAR.log().error("No model parsed from input file!"); + return 3; + } + + return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); + } + + /** + * Informs user about potential information loss occurring during file conversion + * @param inputFileExtension file extension of the input file (lower case, no leading dot) + * @param outputFileExtension file extension of the output file (lower case, no leading dot) + */ + private void infoLossMessage(String inputFileExtension, String outputFileExtension) { + + StringBuilder msg = new StringBuilder(); + msg.append("Info Loss:\n"); + + Map> infoLossMap = buildInfoLossMap(); + + Map iSupports = infoLossMap.get(inputFileExtension); + Map oSupports = infoLossMap.get(outputFileExtension); + + if (iSupports == null || oSupports == null) { + return; + } + boolean infoLossPresent = false; + for (FileInfo fileInfo : iSupports.keySet()) { + SupportLevel iSupportLevel = iSupports.get(fileInfo); + SupportLevel oSupportLevel = oSupports.get(fileInfo); + + if (oSupportLevel.isLessThan(iSupportLevel)) { + if (!infoLossPresent) { + msg.append(String.format( + "%-46s %s%n", "", inputFileExtension + " --> " + outputFileExtension + "\n")); + infoLossPresent = true; + } + + msg.append(String.format("%-36s %14s %5s%n", " " + fileInfo, iSupportLevel, oSupportLevel)); + } + } + if (infoLossPresent) { + FeatJAR.log().warning(msg.toString()); + } else { + FeatJAR.log().info("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); + } + } + + /** + * + * {@return information loss map that tracks how well a file extension supports any given piece of information} + */ + private Map> buildInfoLossMap() { + Map> supportMap = new HashMap<>(); + + buildInfoLossMapRegisterExt( + "xml", + Map.of( + FileInfo.basicHierarchy, SupportLevel.YES, + FileInfo.subgroupHierarchy, SupportLevel.NO, + FileInfo.featureDescription, SupportLevel.YES, + FileInfo.featureAttributes, SupportLevel.YES, + FileInfo.featureCardinality, SupportLevel.NO, + FileInfo.booleanOperators, SupportLevel.YES, + FileInfo.allOperators, SupportLevel.NO, + FileInfo.parseable, SupportLevel.YES), + supportMap); + + buildInfoLossMapRegisterExt( + "uvl", + Map.of( + FileInfo.basicHierarchy, SupportLevel.YES, + FileInfo.subgroupHierarchy, SupportLevel.YES, + FileInfo.featureDescription, SupportLevel.YES, + FileInfo.featureAttributes, SupportLevel.YES, + FileInfo.featureCardinality, SupportLevel.YES, + FileInfo.booleanOperators, SupportLevel.YES, + FileInfo.allOperators, SupportLevel.YES, + FileInfo.parseable, SupportLevel.YES), + supportMap); + + buildInfoLossMapRegisterExt( + "dot", + Map.of( + FileInfo.basicHierarchy, SupportLevel.YES, + FileInfo.subgroupHierarchy, SupportLevel.YES, + FileInfo.featureDescription, SupportLevel.YES, + FileInfo.featureAttributes, SupportLevel.YES, + FileInfo.featureCardinality, SupportLevel.YES, + FileInfo.booleanOperators, SupportLevel.YES, + FileInfo.allOperators, SupportLevel.YES, + FileInfo.parseable, SupportLevel.NO), + supportMap); + + // if user forgot to set FileInfos: Support Level is automatically set to NONE + for (String ext : supportMap.keySet()) { + for (FileInfo fileInfo : FileInfo.values()) { + supportMap.get(ext).putIfAbsent(fileInfo, SupportLevel.NO); + } + } + + return supportMap; + } + + /** + * Reinforces correct addition of infoLossMap entries + * @param extension file extension that will be added + * @param fileInfos pieces of file information as described in FileInfo enum + * @param supportMap the information loss map that's being updated + */ + private void buildInfoLossMapRegisterExt( + String extension, + Map fileInfos, + Map> supportMap) { + if (fileInfos.size() != FileInfo.values().length) { + FeatJAR.log() + .error("Info Loss Map: " + extension + + " was added with too many or too few FileInfos. Skipping this extension."); + return; + } + supportMap.put(extension, new EnumMap<>(fileInfos)); + } + + /** + * Checks if input and output file extensions provided by user appear in list of supported extensions. + * @param inputFileExtension: extension used for the input file + * @param outputFileExtension extension used for the output file + * @return true if both extensions are valid, false if either is invalid + */ + private boolean checkIfFileExtensionsValid(String inputFileExtension, String outputFileExtension) { + if (!supportedInputFileExtensions.contains(inputFileExtension)) { + FeatJAR.log() + .error("Unsupported input file extension.\n" + + "Received extension: " + inputFileExtension + "\nSupported extensions: " + + supportedInputFileExtensions); + return false; + } + + if (!supportedOutputFileExtensions.contains(outputFileExtension)) { + FeatJAR.log() + .error("Unsupported output file extension.\n" + + "Received extension: " + outputFileExtension + "\nSupported extensions: " + + supportedOutputFileExtensions); + return false; + } + return true; + } + + /** + * + * @param optionParser holds the command line parameters + * {@return true if an input and output path were provided, otherwise false} + */ + private boolean checkIfInputOutputIsPresent(OptionList optionParser) { + if (!optionParser.getResult(INPUT_OPTION).isPresent()) { + FeatJAR.log().error("No input path provided."); + return false; + } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { + FeatJAR.log().error("No output path provided."); + return false; + } + return true; + } + + /** + * Attempts to extract a feature model from the input file. + * @param optionParser holds the command line parameters + * @return Feature Model read out from input file. Will be null on failure. + */ + private IFeatureModel inputParser(OptionList optionParser) { + Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); + IFeatureModel model = null; + try { + Result load = IO.load(inputPath, FeatureModelFormats.getInstance()); + model = load.get(); + } catch (Exception e) { + FeatJAR.log().error(e.getMessage()); + } + return model; + } + + /** + * Saves the read feature model as the desired output file. Automatically fetches the appropriate format. Does error handling. + * @param outputPath Full path to output file. + * @param model Feature Model to be saved into the output file. + * @param outputFileExtension extension of the output file. Used to fetch appropriate format. + * @param overWriteOutputFile flag that decides whether existing output files with the same name should be overwritten. + * @return 0 on success + * 2 if an input/output file type is invalid + * 4 if a file is already present at output path and no overwrite is specified + * 5 on IOException + */ + public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { + + IFormat format; + + Optional> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) + .findFirst(); + if (outputFormats.isEmpty()) { + FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); + return 2; + } else { + format = outputFormats.get(); + } + + try { + if (Files.exists(outputPath)) { + if (overWriteOutputFile) { + FeatJAR.log().info("File already present at: " + outputPath + ". Continuing to overwrite File."); + } else { + FeatJAR.log() + .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath + + ". To overwrite present file add --overwrite"); + return 4; + } + } + IO.save(model, outputPath, format); + + } catch (IOException e) { + FeatJAR.log().error(e); + return 5; + } + FeatJAR.log().message("Output model saved at: " + outputPath); + return 0; + } + + /** + * + * {@return brief description of this class} + */ + @Override + public Optional getDescription() { + return Optional.of("Convert configuration Format into new configuration format."); + } + + /** + * + * {@return short name of this class} + */ + @Override + public Optional getShortName() { + return Optional.of("configurationFormatConversion"); + } +} diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index af5fdaa9..ac12e441 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -36,6 +36,7 @@ import de.featjar.feature.model.computation.ComputeFeatureDensity; import de.featjar.feature.model.computation.ComputeOperatorDistribution; import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.feature.model.analysis.AnalysisTree; import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashMap; @@ -97,7 +98,7 @@ public int run(OptionList optionParser) { if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { Path outputPath = optionParser.get(OUTPUT_OPTION); String fileExtension = IO.getFileExtension(outputPath); - writeTo(optionParser.getResult(OUTPUT_OPTION).get(), fileExtension); + writeTo(optionParser.getResult(OUTPUT_OPTION).get(), fileExtension, data); } // printing statistics to console if no output file is specified @@ -115,12 +116,15 @@ public int run(OptionList optionParser) { * @param path: full path to output file * @param type: is extracted from provided output path, needs to be lower case */ - private void writeTo(Path path, String type) { + private void writeTo(Path path, String type,LinkedHashMap data) { switch (type) { case "xml": // TODO future Story Card: Write to XML // IO.save(new Object(data), path, new XMLFeatureModelFormat()); + AnalysisTree tree = AnalysisTree.hashMapToTree(data, type); + System.out.println(tree.toString()); + break; case "csv": // TODO future Story Card: Write to CSV diff --git a/src/main/resources/extensions.xml b/src/main/resources/extensions.xml index f5cef2cd..87fcd62b 100644 --- a/src/main/resources/extensions.xml +++ b/src/main/resources/extensions.xml @@ -7,5 +7,6 @@ + From a6acd9871905ce9e85aca4a0bb75440fda123c72 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Mon, 13 Oct 2025 13:34:23 +0200 Subject: [PATCH 151/257] tests: adjusted tests for io --- src/test/java/de/featjar/feature/model/io/CSVExportTest.java | 4 ++-- src/test/java/de/featjar/feature/model/io/JSONExportTest.java | 2 ++ src/test/java/de/featjar/feature/model/io/YAMLExportTest.java | 2 ++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java index c8c3c6c0..b6e719ee 100644 --- a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java @@ -32,7 +32,7 @@ public class CSVExportTest { public AnalysisTree createDefaultTree() { AnalysisTree innereanalysisTree = new AnalysisTree<>( "avgNumOfAtomsPerConstraints", - new AnalysisTree<>("xo", 3.3), + new AnalysisTree<>("test property", 3.3), new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); AnalysisTree analysisTree = new AnalysisTree<>( @@ -64,7 +64,7 @@ public void CSVTest() throws IOException { + "Analysis;numInAltGroups;5;java.lang.Integer\n" + "Analysis;numOfAtoms;8;java.lang.Integer\n" + "Analysis;avgNumOfAsss;4;java.lang.Integer\n" - + "avgNumOfAtomsPerConstraints;xo;3.3;java.lang.Double\n" + + "avgNumOfAtomsPerConstraints;test property;3.3;java.lang.Double\n" + "avgNumOfAtomsPerConstraints;numOfLeafFeatures;12.4;java.lang.Float\n"); } } diff --git a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java index 5ba6a343..19ca41d3 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java @@ -29,6 +29,7 @@ import de.featjar.feature.model.io.json.JSONAnalysisFormat; import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.LinkedHashMap; @@ -108,5 +109,6 @@ public void JSONSaveLoadTest() throws IOException { assertTrue( Trees.equals(analysisTree, outputAnalysisTree), "firstTree\n" + analysisTree.print() + "\nsecond tree\n" + outputAnalysisTree.print()); + Files.deleteIfExists(Paths.get("filename.json")); } } diff --git a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java index 866d760d..832fc513 100644 --- a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java @@ -28,6 +28,7 @@ import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; import java.util.LinkedHashMap; @@ -70,6 +71,7 @@ public void YAMLTest() throws IOException { assertTrue( Trees.equals(analysisTree, outputAnalysisTree), "firstTree\n" + analysisTree.print() + "\nsecond tree\n" + outputAnalysisTree.print()); + Files.deleteIfExists(Paths.get("filename.yaml")); } @Test From f528650780fc78999c03b07b67d6292f36dc4898 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Mon, 13 Oct 2025 14:21:04 +0200 Subject: [PATCH 152/257] test: modified one test --- .../feature/model/io/CSVExportTest.java | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java index b6e719ee..898e40ff 100644 --- a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java @@ -22,9 +22,12 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import de.featjar.base.io.IO; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.io.csv.CSVAnalysisFormat; import java.io.IOException; +import java.nio.file.Paths; + import org.junit.jupiter.api.Test; public class CSVExportTest { @@ -53,18 +56,19 @@ public AnalysisTree createDefaultTree() { public void CSVTest() throws IOException { CSVAnalysisFormat csvAnalysisFormat = new CSVAnalysisFormat(); String csvString = csvAnalysisFormat.serialize(createDefaultTree()).orElseThrow(); + IO.save(createDefaultTree(), Paths.get("file.csv"), csvAnalysisFormat); assertEquals( csvString, - "AnalysisType;Name;Value;Class\n" - + "Analysis;numOfLeafFeatures;12.4;java.lang.Float\n" - + "Analysis;numOfTopFeatures;3.3;java.lang.Double\n" - + "Analysis;treeDepth;3;java.lang.Integer\n" - + "Analysis;avgNumOfChildren;3;java.lang.Integer\n" - + "Analysis;numInOrGroups;7;java.lang.Integer\n" - + "Analysis;numInAltGroups;5;java.lang.Integer\n" - + "Analysis;numOfAtoms;8;java.lang.Integer\n" - + "Analysis;avgNumOfAsss;4;java.lang.Integer\n" - + "avgNumOfAtomsPerConstraints;test property;3.3;java.lang.Double\n" - + "avgNumOfAtomsPerConstraints;numOfLeafFeatures;12.4;java.lang.Float\n"); + "AnalysisType;Name;Class;Value\n" + + "Analysis;numOfLeafFeatures;java.lang.Float;12.4\n" + + "Analysis;numOfTopFeatures;java.lang.Double;3.3\n" + + "Analysis;treeDepth;java.lang.Integer;3\n" + + "Analysis;avgNumOfChildren;java.lang.Integer;3\n" + + "Analysis;numInOrGroups;java.lang.Integer;7\n" + + "Analysis;numInAltGroups;java.lang.Integer;5\n" + + "Analysis;numOfAtoms;java.lang.Integer;8\n" + + "Analysis;avgNumOfAsss;java.lang.Integer;4\n" + + "avgNumOfAtomsPerConstraints;test property;java.lang.Double;3.3\n" + + "avgNumOfAtomsPerConstraints;numOfLeafFeatures;java.lang.Float;12.4\n"); } } From ce526be537511af3d3d84c82daff5098b3b3eb08 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Mon, 13 Oct 2025 14:25:33 +0200 Subject: [PATCH 153/257] style: formatting by gradlew spotless --- .../feature/model/cli/FormatConversion.java | 29 +++++++++---------- .../feature/model/io/CSVExportTest.java | 21 +++++++------- 2 files changed, 23 insertions(+), 27 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java index f54ea31b..dfdcbd90 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FormatConversion.java @@ -32,15 +32,12 @@ import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.EnumMap; +import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Objects; import java.util.Optional; -import java.util.EnumMap; -import java.util.HashMap; - - - import java.util.stream.Collectors; /** @@ -50,17 +47,17 @@ */ public class FormatConversion implements ICommand { - private static final List supportedInputFileExtensions = FeatureModelFormats.getInstance().getExtensions() - .stream() - .filter(IFormat::supportsParse) - .map(IFormat::getFileExtension) - .collect(Collectors.toList()); - - private static final List supportedOutputFileExtensions = FeatureModelFormats.getInstance().getExtensions() - .stream() - .filter(IFormat::supportsWrite) - .map(IFormat::getFileExtension) - .collect(Collectors.toList()); + private static final List supportedInputFileExtensions = + FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsParse) + .map(IFormat::getFileExtension) + .collect(Collectors.toList()); + + private static final List supportedOutputFileExtensions = + FeatureModelFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .map(IFormat::getFileExtension) + .collect(Collectors.toList()); public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) diff --git a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java index 898e40ff..ffd51eb5 100644 --- a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java @@ -27,7 +27,6 @@ import de.featjar.feature.model.io.csv.CSVAnalysisFormat; import java.io.IOException; import java.nio.file.Paths; - import org.junit.jupiter.api.Test; public class CSVExportTest { @@ -60,15 +59,15 @@ public void CSVTest() throws IOException { assertEquals( csvString, "AnalysisType;Name;Class;Value\n" - + "Analysis;numOfLeafFeatures;java.lang.Float;12.4\n" - + "Analysis;numOfTopFeatures;java.lang.Double;3.3\n" - + "Analysis;treeDepth;java.lang.Integer;3\n" - + "Analysis;avgNumOfChildren;java.lang.Integer;3\n" - + "Analysis;numInOrGroups;java.lang.Integer;7\n" - + "Analysis;numInAltGroups;java.lang.Integer;5\n" - + "Analysis;numOfAtoms;java.lang.Integer;8\n" - + "Analysis;avgNumOfAsss;java.lang.Integer;4\n" - + "avgNumOfAtomsPerConstraints;test property;java.lang.Double;3.3\n" - + "avgNumOfAtomsPerConstraints;numOfLeafFeatures;java.lang.Float;12.4\n"); + + "Analysis;numOfLeafFeatures;java.lang.Float;12.4\n" + + "Analysis;numOfTopFeatures;java.lang.Double;3.3\n" + + "Analysis;treeDepth;java.lang.Integer;3\n" + + "Analysis;avgNumOfChildren;java.lang.Integer;3\n" + + "Analysis;numInOrGroups;java.lang.Integer;7\n" + + "Analysis;numInAltGroups;java.lang.Integer;5\n" + + "Analysis;numOfAtoms;java.lang.Integer;8\n" + + "Analysis;avgNumOfAsss;java.lang.Integer;4\n" + + "avgNumOfAtomsPerConstraints;test property;java.lang.Double;3.3\n" + + "avgNumOfAtomsPerConstraints;numOfLeafFeatures;java.lang.Float;12.4\n"); } } From 8b6b4fb04483557b4f1258d074f10759cb6ccabf Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 13 Oct 2025 15:32:34 +0200 Subject: [PATCH 154/257] chore: added XChart dependency to gradle --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index d6fa2a7b..fafffdf8 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ plugins { dependencies { api 'de.featjar:formula' api testFixtures('de.featjar:formula') + implementation 'org.knowm.xchart:xchart:3.8.8' } license { From ce96e37242260564007ebe6ae778ae7885f2f679 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Mon, 13 Oct 2025 16:16:28 +0200 Subject: [PATCH 155/257] feat: overwrite option for printstats. integrated with components to save stats as json, csv and yaml --- .../cli/ConfigurationFormatConversion.java | 4 +- .../feature/model/cli/PrintStatistics.java | 128 ++++++++++-------- .../model/cli/PrintStatisticsTest.java | 121 +++++++++++++++-- 3 files changed, 176 insertions(+), 77 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 8d87171e..903cf35a 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -29,8 +29,6 @@ import de.featjar.base.io.format.IFormat; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.io.FeatureModelFormats; -import de.featjar.formula.assignment.BooleanAssignmentValueMap; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -48,7 +46,7 @@ * @author Knut, Kilian & Benjamin */ -// BooleanAssignmentValueMapFormat implements IFormat +// BooleanAssignmentValueMapFormat implements IFormat public class ConfigurationFormatConversion implements ICommand { diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index ac12e441..1a1b435c 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -30,13 +30,23 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; -import de.featjar.feature.model.analysis.*; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.ComputeFeatureAverageNumberOfChildren; +import de.featjar.feature.model.analysis.ComputeFeatureFeaturesCounter; +import de.featjar.feature.model.analysis.ComputeFeatureGroupDistribution; +import de.featjar.feature.model.analysis.ComputeFeatureTopFeatures; +import de.featjar.feature.model.analysis.ComputeFeatureTreeDepth; import de.featjar.feature.model.computation.ComputeAtomsCount; import de.featjar.feature.model.computation.ComputeAverageConstraint; import de.featjar.feature.model.computation.ComputeFeatureDensity; import de.featjar.feature.model.computation.ComputeOperatorDistribution; import de.featjar.feature.model.io.FeatureModelFormats; -import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.io.csv.CSVAnalysisFormat; +import de.featjar.feature.model.io.json.JSONAnalysisFormat; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; +import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.util.HashMap; import java.util.LinkedHashMap; @@ -59,13 +69,15 @@ public enum AnalysesScope { private int exit_status = 0; - // command line options for user customization public static final Option ANALYSES_SCOPE = Option.newEnumOption("scope", AnalysesScope.class).setDescription("Specifies scope of statistics"); public static final Option PRETTY_PRINT = Option.newFlag("pretty").setDescription("Pretty prints the numbers"); + public static final Option OVERWRITE = + Option.newFlag("overwrite").setDescription("Overwrite output file."); + /** * main method for gathering, printing and writing statistics of a feature model * @param optionParser the option parser @@ -75,6 +87,7 @@ public enum AnalysesScope { @Override public int run(OptionList optionParser) { + // checking if input model has been specified if (!optionParser.getResult(INPUT_OPTION).isPresent()) { FeatJAR.log().error("No Input file attached"); return 1; @@ -84,7 +97,6 @@ public int run(OptionList optionParser) { Path path = optionParser.getResult(INPUT_OPTION).orElseThrow(); Result load = IO.load(path, FeatureModelFormats.getInstance()); LinkedHashMap data; - FeatureModel model = (FeatureModel) load.orElseThrow(); // collecting statistics of the model, checking if scope is specified @@ -94,65 +106,74 @@ public int run(OptionList optionParser) { data = collectStats(model, AnalysesScope.ALL); } - // if output path is specified, write statistics to file - if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { - Path outputPath = optionParser.get(OUTPUT_OPTION); - String fileExtension = IO.getFileExtension(outputPath); - writeTo(optionParser.getResult(OUTPUT_OPTION).get(), fileExtension, data); - } - - // printing statistics to console if no output file is specified + // printing pretty if PRETTY flag specified, printing only compact if there is no output specified if (optionParser.get(PRETTY_PRINT)) { printStatsPretty(data); } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { printStats(data); } + // if output path is specified, write statistics to file + if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { + + Path outputPath = optionParser.get(OUTPUT_OPTION); + + if (Files.exists(outputPath)) { + if (optionParser.get(OVERWRITE)) { + FeatJAR.log().info("File already present at: " + outputPath + ". Continuing to overwrite File."); + } else { + FeatJAR.log() + .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath + + ".\nTo overwrite present file add --overwrite"); + return 1; + } + } + writeTo(outputPath, data); + FeatJAR.log().message("Feature Model saved at: " + outputPath); + } return exit_status; } /** * writes statistics into a file, depending on file type - * @param path: full path to output file - * @param type: is extracted from provided output path, needs to be lower case + * @param path: full path to output file. can be "csv", "yaml" or "json" + * @param data: expects data about a feature model as LinkedHashMap but will be converted to Result> + * @throws IOException */ - private void writeTo(Path path, String type,LinkedHashMap data) { - - switch (type) { - case "xml": - // TODO future Story Card: Write to XML - // IO.save(new Object(data), path, new XMLFeatureModelFormat()); - AnalysisTree tree = AnalysisTree.hashMapToTree(data, type); - System.out.println(tree.toString()); - - break; - case "csv": - // TODO future Story Card: Write to CSV - break; - case "yaml": - // TODO future Story Card: Write to YAML - break; - case "json": - // TODO future Story Card: Write to JSON - break; - case "txt": - // TODO future Story Card: Write to TXT - break; - case "": - FeatJAR.log().error("Output file does not include file type."); - exit_status = 1; - break; - default: - FeatJAR.log().error("File type not valid: " + type); - exit_status = 1; + private void writeTo(Path path, LinkedHashMap data) { + String type = IO.getFileExtension(path); + + Result> tree = AnalysisTreeTransformer.hashMapToTree(data, type); + + try { + switch (type) { + case "csv": + IO.save(tree.get(), path, new CSVAnalysisFormat()); + break; + case "yaml": + IO.save(tree.get(), path, new YAMLAnalysisFormat()); + break; + case "json": + IO.save(tree.get(), path, new JSONAnalysisFormat()); + break; + case "": + FeatJAR.log().error("Output file does not include file type."); + exit_status = 1; + break; + default: + FeatJAR.log().error("File type not valid: " + type); + exit_status = 1; + } + } catch (Exception e) { + FeatJAR.log().error(e); } } /** * method for collecting statistics of the provided feature model depending on specified scope of information (all, constraint related, tree related) - * @param model: a feature model from which stats will be collected - * @param scope: describes whether only constraint-related, only tree-related, or both kinds of stats are to be collected - * @return LinkedHashMap with stats data, keys are descriptive strings, values types depend on statistic (Integer, Float, HashMap) + * @param model: a feature model from which statistics will be collected + * @param scope: describes whether only constraint-related, only tree-related, or both kinds of statistics are to be collected + * @return LinkedHashMap with statistics data, keys are descriptive strings, values types depend on statistic (Integer, Float, HashMap) */ public LinkedHashMap collectStats(FeatureModel model, AnalysesScope scope) { @@ -170,47 +191,38 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc data.put( "Average Constraints", Computations.of(model).map(ComputeAverageConstraint::new).compute()); - HashMap computational_opDensity = Computations.of(model).map(ComputeOperatorDistribution::new).compute(); - data.put("Operator Distribution", computational_opDensity); } if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { // fetching tree related statistics - List trees = model.getRoots(); String treePrefix; for (int i = 0; i < trees.size(); i++) { treePrefix = "[Tree " + (i + 1) + "] "; - IFeatureTree tree = trees.get(i); - data.put( treePrefix + "Average Number of Children", Computations.of(tree) .map(ComputeFeatureAverageNumberOfChildren::new) .compute()); - data.put( treePrefix + "Number of Top Features", Computations.of(tree) .map(ComputeFeatureTopFeatures::new) .compute()); - data.put( treePrefix + "Number of Leaf Features", Computations.of(tree) .map(ComputeFeatureFeaturesCounter::new) .compute()); - data.put( treePrefix + "Tree Depth", Computations.of(tree).map(ComputeFeatureTreeDepth::new).compute()); - data.put( treePrefix + "Group Distribution", Computations.of(tree) @@ -239,18 +251,14 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) StringBuilder outputString = new StringBuilder(); for (Map.Entry entry : data.entrySet()) { - - if (entry.getKey().equals("Number of Atoms")) { + if (entry.getKey().equals("Number of Atoms")) { outputString.append(String.format("\n %-40s %n", "CONSTRAINT RELATED STATS\n")); - } else if (entry.getKey().equals("[Tree 1] Average Number of Children")) { outputString.append(String.format("\n %-40s %n", "TREE RELATED STATS\n")); } if (entry.getValue() instanceof Map) { Map nestedMap = (Map) entry.getValue(); - outputString.append(String.format("%-40s%n", entry.getKey())); - for (Map.Entry nestedEntry : nestedMap.entrySet()) { outputString.append(String.format( "%-40s : %s%n", " " + nestedEntry.getKey(), nestedEntry.getValue())); diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index a95a4501..81a0f466 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -21,14 +21,27 @@ package de.featjar.feature.model.cli; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import de.featjar.base.FeatJAR; import de.featjar.base.data.identifier.AIdentifier; import de.featjar.base.data.identifier.IIdentifiable; import de.featjar.base.data.identifier.Identifiers; +import de.featjar.base.io.IO; import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; +import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.feature.model.io.json.JSONAnalysisFormat; +import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.LinkedHashMap; +import java.util.Map; + import org.junit.jupiter.api.Test; /** @@ -39,67 +52,86 @@ public class PrintStatisticsTest { PrintStatistics printStats = new PrintStatistics(); - FeatureModel minimalModel = generateMinimalModel(); + FeatureModel minimalModel = generateModel(); - private FeatureModel generateMinimalModel() { + private FeatureModel generateModel() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); return featureModel; } + /** + * Testing the parsing of an input model. + */ @Test - void inputTest() { + void inputTest() throws IOException { int exit_code = FeatJAR.runTest( "printStats", "--input", "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"); assertEquals(0, exit_code); } + /** + * Testing with no input, can't gather statistics without source model. + */ @Test - void noInput() { + void noInput() throws IOException { assertEquals(1, FeatJAR.runTest("printStats", "--input")); assertEquals(1, FeatJAR.runTest("printStats")); } + /** + * Testing input with valid file extension (valid extensions currently are csv, yaml and json). + */ @Test - void outputWithFileValidExtension() { + void outputWithFileValidExtension() throws IOException { int exit_code = FeatJAR.runTest( "printStats", "--input", "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", "--output", - "desktop/folder/model.xml"); + "model_outputWithFileValidExtension.csv"); assertEquals(0, exit_code); + Files.deleteIfExists(Paths.get("model_outputWithFileValidExtension.csv")); } + /** + * Testing with a file extension for which there is no analysis format defined. + */ @Test - void outputWithFileInvalidExtension() { + void outputWithFileInvalidExtension() throws IOException { int exit_code = FeatJAR.runTest( "printStats", "--input", "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", "--output", - "desktop/folder/model.pdf"); + "model_outputWithFileInvalidExtension.pdf"); assertEquals(1, exit_code); } + /** + * Testing without any (legal or not) file extension specified. + */ @Test - void outputWithoutFileExtension() { + void outputWithoutFileExtension() throws IOException { int exit_code = FeatJAR.runTest( "printStats", "--input", "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", "--output", - "desktop/folder"); + "model_outputWithoutFileExtension"); assertEquals(1, exit_code); } + /** + * Testing whether collecting statistics with scope specified to ALL actually returns values for all parameters. + */ @Test - void scopeAll() { + void scopeAll() throws IOException { String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}, [Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; String comparison = @@ -107,8 +139,11 @@ void scopeAll() { assertEquals(content, comparison); } + /** + * Testing whether collecting statistics with scope specified to TREE_RELATED actually returns values for tree related parameters only. + */ @Test - void scopeTreeRelated() { + void scopeTreeRelated() throws IOException { String content = "{[Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; @@ -118,8 +153,11 @@ void scopeTreeRelated() { assertEquals(content, comparison); } + /** + * Testing whether collecting statistics with scope specified to CONSTRAINT_RELATED actually returns values for constraint related parameters only. + */ @Test - void scopeConstraintRelated() { + void scopeConstraintRelated() throws IOException { String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}}"; String comparison = printStats .collectStats(minimalModel, AnalysesScope.CONSTRAINT_RELATED) @@ -127,8 +165,11 @@ void scopeConstraintRelated() { assertEquals(content, comparison); } + /** + * Testing whether using the flag PRETTY actually returns expected string. + */ @Test - void prettyStringBuilder() { + void prettyStringBuilder() throws IOException { LinkedHashMap testData = new LinkedHashMap<>(); testData.put("Normal Entry", 10); @@ -155,4 +196,56 @@ void prettyStringBuilder() { assertEquals(comparison, printStats.buildStringPrettyStats(testData).toString()); } + + /** + * Testing whether CSV output creates correct file + */ + @Test + void jsonOuputTest() throws IOException { + + int exit_code = FeatJAR.runTest( + "printStats", + "--input", + "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", + "--output", + "model_jsonOuputTest.json", + "--overwrite"); + assertEquals(0, exit_code); + + AnalysisTree tree = IO.load(Paths.get("model_jsonOuputTest.json"), new JSONAnalysisFormat()).get(); + + //assertEquals(tree, tree2); + +// FeatJAR.initialize(); +// +// Path outputPath = Paths.get("model_csvOuputTest.xml"); +// LinkedHashMap dummyData = new LinkedHashMap<>(Map.of( +// "Value1", 67, +// "Value2", 4.20, +// "Value3", "Testing" +// )); +// +// Files.deleteIfExists(outputPath); +// +// // let program write model to XML file +// new PrintStatistics().writeTo; +// +// // round trip: rebuild model from XML file +// FeatureModel retrievedModel = +// (FeatureModel) IO.load(outputPath, new XMLFeatureModelFormat()).get(); +// +// assertEquals(model, retrievedModel); +// +// Files.deleteIfExists(outputPath); +// FeatJAR.deinitialize(); + + } + + /** + * Testing whether YAML output creates correct file + */ + @Test + void yamlOuputTest() throws IOException { + + } } From 42055d8e9166d2a74bdb964c8f80bcc4ae97bbd7 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 13 Oct 2025 16:40:26 +0200 Subject: [PATCH 156/257] feat prototype visualization --- .../VisualizeFeatureModelStats.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java new file mode 100644 index 00000000..1c280dbb --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java @@ -0,0 +1,59 @@ +package de.featjar.feature.model.analysis.visualization; + +import org.knowm.xchart.QuickChart; +import org.knowm.xchart.SwingWrapper; +import org.knowm.xchart.XYChart; +import org.knowm.xchart.PieChart; +import org.knowm.xchart.PieChartBuilder; + +public class VisualizeFeatureModelStats { + + private double[] xData; + private double[] yData; + + private String chartTitle; + private String xTitle; + private String yTitle; + private String seriesName = "y(x)"; + + public VisualizeFeatureModelStats(double[] xData, double[] yData) { + this.xData = xData; + this.yData = yData; + this.chartTitle = "Titel"; + this.xTitle = "x-Achse"; + this.yTitle = "y-Achse"; + } + + public VisualizeFeatureModelStats(double[] xData, double[] yData, String chartTitle, String xTitle, String yTitle) { + this.xData = xData; + this.yData = yData; + this.chartTitle = chartTitle; + this.xTitle = xTitle; + this.yTitle = yTitle; + } + + public XYChart getChart() { + return QuickChart.getChart + (this.chartTitle, this.xTitle, this.yTitle, this.seriesName, this.xData, this.yData); + } + + public void displayChart() { + //new Swing + } + public static void main(String[] args) { + // Sample data + double[] xData = new double[] {0.0, 1.0, 2.0}; + double[] yData = new double[] {2.0, 1.0, 0.0}; + + // Create Chart + XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData); + PieChart chart1 = new PieChartBuilder().build(); + + chart1.addSeries("Banananana", 40); + chart1.addSeries("Apple", 25); + chart1.addSeries("Gurke", 50); + + // Display the chart + new SwingWrapper<>(chart1).displayChart(); + } +} From 2e0fcd08b93cb1bfebed4d368ceb5d57725f7027 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Tue, 14 Oct 2025 10:01:39 +0200 Subject: [PATCH 157/257] feat: added computations for sample properties. test: added tests for sample properties --- .../ComputeDistributionFeatureSelections.java | 66 ++++++++++ .../model/analysis/ComputeFeatureCounter.java | 93 ++++++++++++++ .../analysis/ComputeNumberConfigurations.java | 45 +++++++ .../analysis/ComputeNumberVariables.java | 48 +++++++ .../model/analysis/SamplePropertiesTest.java | 119 ++++++++++++++++++ 5 files changed, 371 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java create mode 100644 src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java b/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java new file mode 100644 index 00000000..c31d83f2 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java @@ -0,0 +1,66 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-formula. + * + * formula is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * formula is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with formula. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.formula.assignment.BooleanAssignment; +import de.featjar.formula.assignment.BooleanAssignmentList; +import java.util.HashMap; +import java.util.List; + +public class ComputeDistributionFeatureSelections extends AComputation> { + + protected static final Dependency BOOLEAN_ASSIGMENT_LIST = + Dependency.newDependency(BooleanAssignmentList.class); + + public ComputeDistributionFeatureSelections(IComputation booleanAssigmentList) { + super(booleanAssigmentList); + } + + @Override + public Result> compute(List dependencyList, Progress progress) { + BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + HashMap selectionDistribution = new HashMap(); + selectionDistribution.put("selected", 0); + selectionDistribution.put("deselected", 0); + selectionDistribution.put("undefined", 0); + + for (BooleanAssignment assignment : booleanAssigmenAssignmentList.getAll()) { + selectionDistribution.replace( + "deselected", assignment.countNegatives() + selectionDistribution.get("deselected")); + selectionDistribution.replace( + "selected", assignment.countPositives() + selectionDistribution.get("selected")); + selectionDistribution.replace( + "undefined", + -assignment.countNonZero() + + booleanAssigmenAssignmentList + .getVariableMap() + .getVariableNames() + .size() + + selectionDistribution.get("undefined")); + } + return Result.of(selectionDistribution); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java new file mode 100644 index 00000000..7976e8d4 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java @@ -0,0 +1,93 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-formula. + * + * formula is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * formula is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with formula. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.formula.assignment.BooleanAssignment; +import de.featjar.formula.assignment.BooleanAssignmentList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class ComputeFeatureCounter extends AComputation>> { + + protected static final Dependency BOOLEAN_ASSIGMENT_LIST = + Dependency.newDependency(BooleanAssignmentList.class); + + public ComputeFeatureCounter(IComputation booleanAssigmentList) { + super(booleanAssigmentList); + } + + @Override + public Result>> compute(List dependencyList, Progress progress) { + BooleanAssignmentList booleanAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + HashMap> featureCounter = new HashMap>(); + + // for(String feature : booleanAssignmentList.getVariableMap().getVariableNames()) { + // featureCounter.put(feature,0); + // } + + for (int index : booleanAssignmentList.getVariableMap().getIndices()) { + HashMap tempMap = new HashMap(); + tempMap.put("selected", 0); + tempMap.put("deselected", 0); + tempMap.put("undefined", 0); + featureCounter.put(index, tempMap); + } + + for (BooleanAssignment assignment : booleanAssignmentList.getAll()) { + for (int index : booleanAssignmentList.getVariableMap().getIndices()) { + if (assignment.contains(index)) { + featureCounter + .get(index) + .replace("selected", featureCounter.get(index).get("selected") + 1); + } else if (assignment.containsAnyNegated(index)) { + featureCounter + .get(index) + .replace("deselected", featureCounter.get(index).get("deselected") + 1); + } else { + featureCounter + .get(index) + .replace("undefined", featureCounter.get(index).get("undefined") + 1); + } + } + } + + HashMap> featureNameCounter = new HashMap>(); + + for (Map.Entry> entry : featureCounter.entrySet()) { + if (booleanAssignmentList.getVariableMap().get(entry.getKey()).isPresent()) { + featureNameCounter.put( + booleanAssignmentList + .getVariableMap() + .get(entry.getKey()) + .get(), + entry.getValue()); + } + } + + return Result.of(featureNameCounter); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java new file mode 100644 index 00000000..02f38763 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-formula. + * + * formula is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * formula is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with formula. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.formula.assignment.BooleanAssignmentList; +import java.util.List; + +public class ComputeNumberConfigurations extends AComputation { + + protected static final Dependency BOOLEAN_ASSIGMENT_LIST = + Dependency.newDependency(BooleanAssignmentList.class); + + public ComputeNumberConfigurations(IComputation booleanAssigmentList) { + super(booleanAssigmentList); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + return Result.of(booleanAssigmenAssignmentList.getAll().size()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java new file mode 100644 index 00000000..0e54eced --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java @@ -0,0 +1,48 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-formula. + * + * formula is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * formula is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with formula. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.formula.assignment.BooleanAssignmentList; +import java.util.List; + +public class ComputeNumberVariables extends AComputation { + + protected static final Dependency BOOLEAN_ASSIGMENT_LIST = + Dependency.newDependency(BooleanAssignmentList.class); + + public ComputeNumberVariables(IComputation booleanAssigmentList) { + super(booleanAssigmentList); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + return Result.of(booleanAssigmenAssignmentList + .getVariableMap() + .getVariableNames() + .size()); + } +} diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java new file mode 100644 index 00000000..4dfe0a7a --- /dev/null +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -0,0 +1,119 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-formula. + * + * formula is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * formula is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with formula. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import de.featjar.base.computation.Computations; +import de.featjar.base.computation.IComputation; +import de.featjar.feature.model.analysis.ComputeDistributionFeatureSelections; +import de.featjar.feature.model.analysis.ComputeFeatureCounter; +import de.featjar.feature.model.analysis.ComputeNumberConfigurations; +import de.featjar.feature.model.analysis.ComputeNumberVariables; +import de.featjar.formula.VariableMap; +import de.featjar.formula.assignment.BooleanAssignment; +import de.featjar.formula.assignment.BooleanAssignmentList; +import java.util.HashMap; +import java.util.LinkedList; +import org.junit.jupiter.api.Test; + +public class SamplePropertiesTest { + + public BooleanAssignmentList createAssignmentList() { + LinkedList variableNames = new LinkedList(); + variableNames.add("A"); + variableNames.add("B"); + variableNames.add("C"); + variableNames.add("D"); + variableNames.add("E"); + variableNames.add("F"); + variableNames.add("G"); + VariableMap variableMap = new VariableMap(variableNames); + + BooleanAssignmentList booleanAssignmentList = new BooleanAssignmentList( + variableMap, + new BooleanAssignment(1, -2, -5, -6), + new BooleanAssignment(-1, -3, -6), + new BooleanAssignment(1, 2, 4, 5), + new BooleanAssignment(5, 6), + new BooleanAssignment()); + return booleanAssignmentList; + } + + @Test + public void computeDistributionFeaturesSelectionsTest() { + BooleanAssignmentList booleanAssignmentList = createAssignmentList(); + IComputation> computational = + Computations.of(booleanAssignmentList).map(ComputeDistributionFeatureSelections::new); + HashMap selectionDistribution = computational.compute(); + assertEquals(7, selectionDistribution.get("selected")); + assertEquals(6, selectionDistribution.get("deselected")); + assertEquals(22, selectionDistribution.get("undefined")); + } + + @Test + public void computeFeatureCounterTest() { + BooleanAssignmentList booleanAssignmentList = createAssignmentList(); + IComputation>> computational = + Computations.of(booleanAssignmentList).map(ComputeFeatureCounter::new); + HashMap> featureCounter = computational.compute(); + System.out.println(featureCounter); + assertEquals(2, featureCounter.get("A").get("selected")); + assertEquals(1, featureCounter.get("B").get("selected")); + assertEquals(0, featureCounter.get("C").get("selected")); + assertEquals(1, featureCounter.get("D").get("selected")); + assertEquals(2, featureCounter.get("E").get("selected")); + assertEquals(1, featureCounter.get("F").get("selected")); + assertEquals(0, featureCounter.get("G").get("selected")); + + assertEquals(1, featureCounter.get("A").get("deselected")); + assertEquals(1, featureCounter.get("B").get("deselected")); + assertEquals(1, featureCounter.get("C").get("deselected")); + assertEquals(0, featureCounter.get("D").get("deselected")); + assertEquals(1, featureCounter.get("E").get("deselected")); + assertEquals(2, featureCounter.get("F").get("deselected")); + assertEquals(0, featureCounter.get("G").get("deselected")); + + assertEquals(2, featureCounter.get("A").get("undefined")); + assertEquals(3, featureCounter.get("B").get("undefined")); + assertEquals(4, featureCounter.get("C").get("undefined")); + assertEquals(4, featureCounter.get("D").get("undefined")); + assertEquals(2, featureCounter.get("E").get("undefined")); + assertEquals(2, featureCounter.get("F").get("undefined")); + assertEquals(5, featureCounter.get("G").get("undefined")); + } + + @Test + public void computeNumberConfigurationTest() { + BooleanAssignmentList booleanAssignmentList = createAssignmentList(); + IComputation computational = + Computations.of(booleanAssignmentList).map(ComputeNumberConfigurations::new); + assertEquals(5, computational.compute()); + } + + @Test + public void computeNumberVariablesTest() { + BooleanAssignmentList booleanAssignmentList = createAssignmentList(); + IComputation computational = + Computations.of(booleanAssignmentList).map(ComputeNumberVariables::new); + assertEquals(7, computational.compute()); + } +} From 39e6860bf693ef8bb18dabc9a0f718ee4622f4ae Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 14 Oct 2025 10:40:05 +0200 Subject: [PATCH 158/257] feat: Abstract class for visualizer implemented. + stubs for concrete child classes added. --- .gitignore | 3 +- .../AVisualizeFeatureModelStats.java | 70 +++++++++++++++++++ ...isualizeFeatureConstraintDistribution.java | 23 ++++++ .../VisualizeFeatureGroupDistribution.java | 23 ++++++ .../VisualizeFeatureModelStats.java | 59 ---------------- .../VisualizeFeatureTypeDistribution.java | 24 +++++++ 6 files changed, 142 insertions(+), 60 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java delete mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java diff --git a/.gitignore b/.gitignore index 09049555..b76fe34e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /.classpath /.idea/ /.settings/ -/bin/ \ No newline at end of file +/bin/ +/model_invalidInput.dot diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java new file mode 100644 index 00000000..69f39edf --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -0,0 +1,70 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.SwingWrapper; +import org.knowm.xchart.internal.chartpart.Chart; + +/** + * A class that builds a visualization using the XChart library. + * Data is read as an {@link AnalysisTree} and the chart is built by the buildChart() methods of each child class. + * + * @author Benjamin von Holt + * @author Valentin Laubsch + */ +public abstract class AVisualizeFeatureModelStats { + + final private AnalysisTree analysisTree; + private Chart chart; + + private String chartTitle = "Chart"; + private Integer width; + private Integer height; + + + public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { + this.analysisTree = analysisTree; + this.chart = buildChart(); + } + + public AVisualizeFeatureModelStats(AnalysisTree analysisTree, String chartTitle) { + this(analysisTree); + this.chartTitle = chartTitle; + } + + public Chart getChart() { + return this.chart; + } + + public Integer getWidth() { + return this.width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return this.height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + /** + * + * {@return the chart that will be used by the other class methods} + */ + abstract Chart buildChart(); + + + /** + * Creates a live preview pop-up window of the internally generated chart. + */ + public void displayChart() { + new SwingWrapper<>(this.chart).displayChart(); + } + + // TODO pdf export hinzufügen + +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java new file mode 100644 index 00000000..9b15bac6 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java @@ -0,0 +1,23 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.internal.chartpart.Chart; + +public class VisualizeFeatureConstraintDistribution extends AVisualizeFeatureModelStats{ + public VisualizeFeatureConstraintDistribution(AnalysisTree analysisTree) { + super(analysisTree); + } + + public VisualizeFeatureConstraintDistribution(AnalysisTree analysisTree, String chartTitle) { + super(analysisTree, chartTitle); + } + + /** + * + * {@return the chart that will be used by the other class methods} + */ + @Override + Chart buildChart() { + return null; + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java new file mode 100644 index 00000000..4abdda41 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java @@ -0,0 +1,23 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.internal.chartpart.Chart; + +public class VisualizeFeatureGroupDistribution extends AVisualizeFeatureModelStats{ + public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree) { + super(analysisTree); + } + + public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree, String chartTitle) { + super(analysisTree, chartTitle); + } + + /** + * + * {@return the chart that will be used by the other class methods} + */ + @Override + Chart buildChart() { + return null; + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java deleted file mode 100644 index 1c280dbb..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java +++ /dev/null @@ -1,59 +0,0 @@ -package de.featjar.feature.model.analysis.visualization; - -import org.knowm.xchart.QuickChart; -import org.knowm.xchart.SwingWrapper; -import org.knowm.xchart.XYChart; -import org.knowm.xchart.PieChart; -import org.knowm.xchart.PieChartBuilder; - -public class VisualizeFeatureModelStats { - - private double[] xData; - private double[] yData; - - private String chartTitle; - private String xTitle; - private String yTitle; - private String seriesName = "y(x)"; - - public VisualizeFeatureModelStats(double[] xData, double[] yData) { - this.xData = xData; - this.yData = yData; - this.chartTitle = "Titel"; - this.xTitle = "x-Achse"; - this.yTitle = "y-Achse"; - } - - public VisualizeFeatureModelStats(double[] xData, double[] yData, String chartTitle, String xTitle, String yTitle) { - this.xData = xData; - this.yData = yData; - this.chartTitle = chartTitle; - this.xTitle = xTitle; - this.yTitle = yTitle; - } - - public XYChart getChart() { - return QuickChart.getChart - (this.chartTitle, this.xTitle, this.yTitle, this.seriesName, this.xData, this.yData); - } - - public void displayChart() { - //new Swing - } - public static void main(String[] args) { - // Sample data - double[] xData = new double[] {0.0, 1.0, 2.0}; - double[] yData = new double[] {2.0, 1.0, 0.0}; - - // Create Chart - XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData); - PieChart chart1 = new PieChartBuilder().build(); - - chart1.addSeries("Banananana", 40); - chart1.addSeries("Apple", 25); - chart1.addSeries("Gurke", 50); - - // Display the chart - new SwingWrapper<>(chart1).displayChart(); - } -} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java new file mode 100644 index 00000000..d46fa4ec --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java @@ -0,0 +1,24 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.internal.chartpart.Chart; + +public class VisualizeFeatureTypeDistribution extends AVisualizeFeatureModelStats{ + public VisualizeFeatureTypeDistribution(AnalysisTree analysisTree) { + super(analysisTree); + } + + public VisualizeFeatureTypeDistribution(AnalysisTree analysisTree, String chartTitle) { + super(analysisTree, chartTitle); + } + + /** + * + * {@return the chart that will be used by the other class methods} + */ + @Override + Chart buildChart() { + return null; + } + +} From 5d404ce82e21c02251d6ade4451e3084eb20b86d Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Tue, 14 Oct 2025 11:09:12 +0200 Subject: [PATCH 159/257] feat: started with the implementation of the ComputeUniformity class --- .../model/analysis/ComputeUniformity.java | 34 +++++++++++++++++++ .../model/analysis/SamplePropertiesTest.java | 23 +++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java new file mode 100644 index 00000000..6f62b1b2 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -0,0 +1,34 @@ +package de.featjar.feature.model.analysis; + +import java.util.HashMap; +import java.util.List; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.feature.model.FeatureModel; +import de.featjar.formula.assignment.BooleanAssignmentList; + +public class ComputeUniformity extends AComputation> { + + protected static final Dependency BOOLEAN_ASSIGMENT_LIST = + Dependency.newDependency(BooleanAssignmentList.class); + protected static final Dependency FEATURE_MODEL = + Dependency.newDependency(FeatureModel.class); + + public ComputeUniformity(IComputation booleanAssigmentList, FeatureModel featureModel) { + super(booleanAssigmentList, featureModel); + } + + @Override + public Result> compute(List dependencyList, Progress progress) { + BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + FeatureModel featureModel = FEATURE_MODEL.get(dependencyList); + + + + return Result.empty(); + } +} \ No newline at end of file diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index 4dfe0a7a..c5f83d18 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -22,15 +22,23 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import de.featjar.base.FeatJAR; import de.featjar.base.computation.Computations; import de.featjar.base.computation.IComputation; +import de.featjar.base.data.Result; +import de.featjar.base.io.IO; +import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.analysis.ComputeDistributionFeatureSelections; import de.featjar.feature.model.analysis.ComputeFeatureCounter; import de.featjar.feature.model.analysis.ComputeNumberConfigurations; import de.featjar.feature.model.analysis.ComputeNumberVariables; +import de.featjar.feature.model.io.FeatureModelFormats; import de.featjar.formula.VariableMap; import de.featjar.formula.assignment.BooleanAssignment; import de.featjar.formula.assignment.BooleanAssignmentList; + +import java.nio.file.Files; +import java.nio.file.Paths; import java.util.HashMap; import java.util.LinkedList; import org.junit.jupiter.api.Test; @@ -116,4 +124,19 @@ public void computeNumberVariablesTest() { Computations.of(booleanAssignmentList).map(ComputeNumberVariables::new); assertEquals(7, computational.compute()); } + + @Test + public void computeUniformity() { + FeatJAR.initialize(); + if (Files.exists(Paths.get("../formula/src/testFixtures/resources/Automotive02_V1"))) { + System.out.println("The file exists."); + } else { + System.out.println("The file does not exist."); + } + + Result featureModelFormatResult = Result.empty(); + featureModelFormatResult.of(IO.load(Paths.get("../formula/src/testFixtures/resources/Automotive02_V1") + , FeatureModelFormats.getInstance()).get()); + System.out.println(featureModelFormatResult.get().getNumberOfConstraints()); + } } From 5665249e7d9956f96c4646f0e6bed1e73056806e Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 14 Oct 2025 12:43:32 +0200 Subject: [PATCH 160/257] test: implemented tests for file writing formats (WIP-json) --- .../feature/model/cli/PrintStatistics.java | 11 +- .../model/cli/PrintStatisticsTest.java | 145 +++++++++++------- .../resources/expected_jsonOutputTest.json | 59 +++++++ .../model/cli/resources/expected_scopeAll.txt | 1 + .../cli/resources/expected_yamlOuputTest.yaml | 16 ++ .../model/cli/resources/simpleTestModel.xml | 18 +++ 6 files changed, 190 insertions(+), 60 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/expected_scopeAll.txt create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 1a1b435c..ee90b81e 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -142,9 +142,8 @@ public int run(OptionList optionParser) { */ private void writeTo(Path path, LinkedHashMap data) { String type = IO.getFileExtension(path); - Result> tree = AnalysisTreeTransformer.hashMapToTree(data, type); - + try { switch (type) { case "csv": @@ -191,9 +190,13 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc data.put( "Average Constraints", Computations.of(model).map(ComputeAverageConstraint::new).compute()); + HashMap computational_opDensity = Computations.of(model).map(ComputeOperatorDistribution::new).compute(); - data.put("Operator Distribution", computational_opDensity); + + if (computational_opDensity.size() != 0) { + data.put("Operator Distribution", computational_opDensity); + } } if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { @@ -251,7 +254,7 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) StringBuilder outputString = new StringBuilder(); for (Map.Entry entry : data.entrySet()) { - if (entry.getKey().equals("Number of Atoms")) { + if (entry.getKey().equals("Number of Atoms")) { outputString.append(String.format("\n %-40s %n", "CONSTRAINT RELATED STATS\n")); } else if (entry.getKey().equals("[Tree 1] Average Number of Children")) { outputString.append(String.format("\n %-40s %n", "TREE RELATED STATS\n")); diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 81a0f466..35299a55 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -21,7 +21,6 @@ package de.featjar.feature.model.cli; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import de.featjar.base.FeatJAR; import de.featjar.base.data.identifier.AIdentifier; @@ -31,17 +30,11 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; -import de.featjar.feature.model.io.FeatureModelFormats; -import de.featjar.feature.model.io.json.JSONAnalysisFormat; -import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; - +import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; -import java.util.Map; - import org.junit.jupiter.api.Test; /** @@ -67,7 +60,7 @@ private FeatureModel generateModel() { void inputTest() throws IOException { int exit_code = FeatJAR.runTest( - "printStats", "--input", "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"); + "printStats", "--input", "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml"); assertEquals(0, exit_code); } @@ -90,9 +83,10 @@ void outputWithFileValidExtension() throws IOException { int exit_code = FeatJAR.runTest( "printStats", "--input", - "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", + "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", "--output", - "model_outputWithFileValidExtension.csv"); + "model_outputWithFileValidExtension.csv", + "--overwrite"); assertEquals(0, exit_code); Files.deleteIfExists(Paths.get("model_outputWithFileValidExtension.csv")); } @@ -106,9 +100,10 @@ void outputWithFileInvalidExtension() throws IOException { int exit_code = FeatJAR.runTest( "printStats", "--input", - "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", + "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", "--output", - "model_outputWithFileInvalidExtension.pdf"); + "model_outputWithFileInvalidExtension.pdf", + "--overwrite"); assertEquals(1, exit_code); } @@ -121,9 +116,10 @@ void outputWithoutFileExtension() throws IOException { int exit_code = FeatJAR.runTest( "printStats", "--input", - "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", + "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", "--output", - "model_outputWithoutFileExtension"); + "model_outputWithoutFileExtension", + "--overwrite"); assertEquals(1, exit_code); } @@ -133,7 +129,7 @@ void outputWithoutFileExtension() throws IOException { @Test void scopeAll() throws IOException { String content = - "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}, [Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, [Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; String comparison = printStats.collectStats(minimalModel, AnalysesScope.ALL).toString(); assertEquals(content, comparison); @@ -158,10 +154,11 @@ void scopeTreeRelated() throws IOException { */ @Test void scopeConstraintRelated() throws IOException { - String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}}"; + String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN}" + ""; String comparison = printStats .collectStats(minimalModel, AnalysesScope.CONSTRAINT_RELATED) .toString(); + assertEquals(content, comparison); } @@ -196,56 +193,92 @@ void prettyStringBuilder() throws IOException { assertEquals(comparison, printStats.buildStringPrettyStats(testData).toString()); } - + // TODO implement this test once the jsonHashMapToTree() function works in AnalysisTreeTransformer + // /** + // * Testing whether JSON output creates correct file + // */ + // @Test + // void jsonOuputTest() throws IOException { + // + // int exit_code = FeatJAR.runTest( + // "printStats", + // "--input", + // "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", + // "--output", + // "model_jsonOuputTest.json", + // "--overwrite"); + // + // AnalysisTree tree = IO.load(Paths.get("model_jsonOuputTest.json"), new JSONAnalysisFormat()).get(); + // AnalysisTree tree_expected = + // IO.load(Paths.get("src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOuputTest.json"), new + // YAMLAnalysisFormat()).get(); + // + // assertEquals(tree.print(), tree_expected.print()); + // assertEquals(0, exit_code); + // + // Files.deleteIfExists(Paths.get("model_jsonOuputTest.json")); + // } + /** - * Testing whether CSV output creates correct file + * Testing whether YAML output creates correct file */ @Test - void jsonOuputTest() throws IOException { - - int exit_code = FeatJAR.runTest( + void yamlOutputTest() throws IOException { + + int exit_code = FeatJAR.runTest( "printStats", "--input", - "../formula/src/testFixtures/resources/Automotive02_V1/model.xml", + "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", "--output", - "model_jsonOuputTest.json", + "model_yamlOuputTest.yaml", "--overwrite"); + + AnalysisTree tree = IO.load(Paths.get("model_yamlOuputTest.yaml"), new YAMLAnalysisFormat()) + .get(); + AnalysisTree tree_expected = IO.load( + Paths.get("src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml"), + new YAMLAnalysisFormat()) + .get(); + + assertEquals(tree.print(), tree_expected.print()); assertEquals(0, exit_code); - - AnalysisTree tree = IO.load(Paths.get("model_jsonOuputTest.json"), new JSONAnalysisFormat()).get(); - - //assertEquals(tree, tree2); - -// FeatJAR.initialize(); -// -// Path outputPath = Paths.get("model_csvOuputTest.xml"); -// LinkedHashMap dummyData = new LinkedHashMap<>(Map.of( -// "Value1", 67, -// "Value2", 4.20, -// "Value3", "Testing" -// )); -// -// Files.deleteIfExists(outputPath); -// -// // let program write model to XML file -// new PrintStatistics().writeTo; -// -// // round trip: rebuild model from XML file -// FeatureModel retrievedModel = -// (FeatureModel) IO.load(outputPath, new XMLFeatureModelFormat()).get(); -// -// assertEquals(model, retrievedModel); -// -// Files.deleteIfExists(outputPath); -// FeatJAR.deinitialize(); - + + Files.deleteIfExists(Paths.get("model_yamlOuputTest.yaml")); } - + /** - * Testing whether YAML output creates correct file + * Testing whether csv output creates correct file */ @Test - void yamlOuputTest() throws IOException { - + void csvOutputTest() throws IOException { + + int exit_code = FeatJAR.runTest( + "printStats", + "--input", + "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", + "--output", + "model_csvOuputTest.csv", + "--overwrite"); + + // CSVAnaylsisFormat() does not support parsing, so this test uses java.nio.file.Files + String content = Files.readString(Paths.get("model_csvOuputTest.csv")); + + String expected = "AnalysisType;Name;Class;Value\n" + + "csv;Number of Atoms;java.lang.Integer;1\n" + + "csv;Feature Density;java.lang.Float;0.33333334\n" + + "csv;Average Constraints;java.lang.Float;1.0\n" + + "csv;[Tree 1] Average Number of Children;java.lang.Double;0.6666666666666666\n" + + "csv;[Tree 1] Number of Top Features;java.lang.Integer;2\n" + + "csv;[Tree 1] Number of Leaf Features;java.lang.Integer;2\n" + + "csv;[Tree 1] Tree Depth;java.lang.Integer;2\n" + + "[Tree 1] Group Distribution;AlternativeGroup;java.lang.Integer;0\n" + + "[Tree 1] Group Distribution;AndGroup;java.lang.Integer;3\n" + + "[Tree 1] Group Distribution;OtherGroup;java.lang.Integer;0\n" + + "[Tree 1] Group Distribution;OrGroup;java.lang.Integer;0\n"; + + assertEquals(expected, content); + assertEquals(0, exit_code); + + Files.deleteIfExists(Paths.get("model_csvOuputTest.csv")); } } diff --git a/src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json b/src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json new file mode 100644 index 00000000..a49170da --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json @@ -0,0 +1,59 @@ +{"json": { + "Number of Atoms": [ + "Number of Atoms", + "java.lang.Integer", + 1 + ], + "Feature Density": [ + "Feature Density", + "java.lang.Float", + 0.33333334 + ], + "Average Constraints": [ + "Average Constraints", + "java.lang.Float", + 1 + ], + "[Tree 1] Group Distribution": { + "AlternativeGroup": [ + "AlternativeGroup", + "java.lang.Integer", + 0 + ], + "AndGroup": [ + "AndGroup", + "java.lang.Integer", + 3 + ], + "OtherGroup": [ + "OtherGroup", + "java.lang.Integer", + 0 + ], + "OrGroup": [ + "OrGroup", + "java.lang.Integer", + 0 + ] + }, + "[Tree 1] Number of Top Features": [ + "[Tree 1] Number of Top Features", + "java.lang.Integer", + 2 + ], + "[Tree 1] Average Number of Children": [ + "[Tree 1] Average Number of Children", + "java.lang.Double", + 0.6666666666666666 + ], + "[Tree 1] Tree Depth": [ + "[Tree 1] Tree Depth", + "java.lang.Integer", + 2 + ], + "[Tree 1] Number of Leaf Features": [ + "[Tree 1] Number of Leaf Features", + "java.lang.Integer", + 2 + ] +}} diff --git a/src/test/java/de/featjar/feature/model/cli/resources/expected_scopeAll.txt b/src/test/java/de/featjar/feature/model/cli/resources/expected_scopeAll.txt new file mode 100644 index 00000000..26e2081c --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/resources/expected_scopeAll.txt @@ -0,0 +1 @@ +{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, Operator Distribution={}, [Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}} \ No newline at end of file diff --git a/src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml b/src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml new file mode 100644 index 00000000..68ab1333 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml @@ -0,0 +1,16 @@ +yaml: + Number of Atoms: [Number of Atoms, java.lang.Integer, 1] + Feature Density: [Feature Density, java.lang.Float, 0.33333334] + Average Constraints: [Average Constraints, java.lang.Float, 1.0] + '[Tree 1] Group Distribution': + AlternativeGroup: [AlternativeGroup, java.lang.Integer, 0] + AndGroup: [AndGroup, java.lang.Integer, 3] + OtherGroup: [OtherGroup, java.lang.Integer, 0] + OrGroup: [OrGroup, java.lang.Integer, 0] + '[Tree 1] Number of Top Features': ['[Tree 1] Number of Top Features', java.lang.Integer, + 2] + '[Tree 1] Average Number of Children': ['[Tree 1] Average Number of Children', java.lang.Double, + 0.6666666666666666] + '[Tree 1] Tree Depth': ['[Tree 1] Tree Depth', java.lang.Integer, 2] + '[Tree 1] Number of Leaf Features': ['[Tree 1] Number of Leaf Features', java.lang.Integer, + 2] diff --git a/src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml b/src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml new file mode 100644 index 00000000..afc3ed39 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml @@ -0,0 +1,18 @@ + + + + + + + + + + + + Base + + + + + + From f74137f62f1cea87df0e83e450d20be17fb881af Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 14 Oct 2025 12:58:51 +0200 Subject: [PATCH 161/257] fix: relaced wrong input file --- .../de/featjar/feature/model/cli/FormatConversionTest.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index 7f444979..c8b2485c 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -49,7 +49,7 @@ private FeatureModel generateModel() { return featureModel; } - private String inputPath = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; + private String inputPath = "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml"; private String outputPath; /** @@ -76,7 +76,7 @@ void fileWritingTest() throws IOException { @Test void inputNotPresent() throws IOException { - inputPath = "../formula/src/testFixtures/resources/Automotive02_V1/model.pdf"; + inputPath = "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.pdf"; outputPath = "model_inputNotPresent.xml"; Files.deleteIfExists(Paths.get(outputPath)); @@ -132,7 +132,6 @@ void modelPresentNoOverwrite() { @Test void infoLossMapTestTriggers() throws IOException { - inputPath = "../formula/src/testFixtures/resources/Automotive02_V1/model.xml"; outputPath = "model_invalidInput.dot"; Files.deleteIfExists(Paths.get(outputPath)); From 248571351ecd6ab2c3d04c8a0b5265b9164db0ac Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Tue, 14 Oct 2025 15:39:48 +0200 Subject: [PATCH 162/257] fix: corrected behavior of ComputeFormula to take constraints into account. feat(WIP): first approach to compute sample uniformity --- build.gradle | 1 + .../model/analysis/ComputeUniformity.java | 46 ++++++++++++++---- .../model/transformer/ComputeFormula.java | 6 +++ .../model/analysis/SamplePropertiesTest.java | 47 ++++++++++++++++++- 4 files changed, 90 insertions(+), 10 deletions(-) diff --git a/build.gradle b/build.gradle index 45fa0bb9..270b6de7 100644 --- a/build.gradle +++ b/build.gradle @@ -6,6 +6,7 @@ plugins { dependencies { api 'de.featjar:formula' api testFixtures('de.featjar:formula') + api 'de.featjar:formula-analysis-javasmt' implementation 'org.json:json:20240303' implementation 'org.yaml:snakeyaml:2.5' implementation 'tools.jackson.dataformat:jackson-dataformat-csv:3.0.0' diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java index 6f62b1b2..4dc307d3 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -1,34 +1,64 @@ package de.featjar.feature.model.analysis; +import java.math.BigInteger; import java.util.HashMap; import java.util.List; import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Computations; import de.featjar.base.computation.Dependency; import de.featjar.base.computation.IComputation; import de.featjar.base.computation.Progress; import de.featjar.base.data.Result; import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.transformer.ComputeFormula; +import de.featjar.formula.VariableMap; import de.featjar.formula.assignment.BooleanAssignmentList; +import de.featjar.formula.computation.ComputeCNFFormula; +import de.featjar.formula.computation.ComputeNNFFormula; +import de.featjar.formula.structure.IFormula; +import de.featjar.formula.structure.connective.And; +import de.featjar.formula.structure.connective.Reference; +import de.featjar.formula.structure.predicate.Literal; +import de.featjar.formula.structure.term.value.Variable; +import de.featjar.feature.model.IFeatureModel; +import de.featjar.analysis.javasmt.computation.ComputeSolutionCount; public class ComputeUniformity extends AComputation> { protected static final Dependency BOOLEAN_ASSIGMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); - protected static final Dependency FEATURE_MODEL = - Dependency.newDependency(FeatureModel.class); + protected static final Dependency FEATURE_MODEL = + Dependency.newDependency(IFeatureModel.class); - public ComputeUniformity(IComputation booleanAssigmentList, FeatureModel featureModel) { - super(booleanAssigmentList, featureModel); + public ComputeUniformity(IComputation featureModel) { + super(Computations.of(new BooleanAssignmentList(new VariableMap())), featureModel); } @Override public Result> compute(List dependencyList, Progress progress) { BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); - FeatureModel featureModel = FEATURE_MODEL.get(dependencyList); + IFeatureModel featureModel = FEATURE_MODEL.get(dependencyList); + IComputation iFormula = Computations.of(featureModel).map(ComputeFormula::new); + IFormula fmFormula = iFormula.compute(); + //new VariableMap(fmFormula); + System.out.println(fmFormula.print()); + IComputation solutionCountComputation = Computations.of(fmFormula).map(ComputeNNFFormula::new) + .map(ComputeCNFFormula::new).map(ComputeSolutionCount::new); + BigInteger solutionsCount = solutionCountComputation.compute(); + HashMap returnMap = new HashMap(); + HashMap fmMap = new HashMap(); + fmMap.put("all", solutionsCount.floatValue()); - - - return Result.empty(); + for (String varName : new VariableMap(fmFormula).getVariableNames()) { + Reference currentFormula = new Reference(new And((IFormula)fmFormula.getChildren().get(0), new Literal(varName))); + currentFormula.setFreeVariables(((Reference)fmFormula).getFreeVariables()); + System.out.println(currentFormula.print()); + IFormula NNFFormula = Computations.of((IFormula)currentFormula).map(ComputeNNFFormula::new).compute(); + fmMap.put(varName, Computations.of(NNFFormula) + .map(ComputeCNFFormula::new).map(ComputeSolutionCount::new).compute().floatValue()); + + } + return Result.of(fmMap); } } \ No newline at end of file diff --git a/src/main/java/de/featjar/feature/model/transformer/ComputeFormula.java b/src/main/java/de/featjar/feature/model/transformer/ComputeFormula.java index dcacf5bb..c1c017ae 100644 --- a/src/main/java/de/featjar/feature/model/transformer/ComputeFormula.java +++ b/src/main/java/de/featjar/feature/model/transformer/ComputeFormula.java @@ -27,6 +27,7 @@ import de.featjar.base.data.Range; import de.featjar.base.data.Result; import de.featjar.feature.model.FeatureTree.Group; +import de.featjar.feature.model.IConstraint; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; @@ -84,6 +85,11 @@ public Result compute(List dependencyList, Progress progress) } handleGroups(constraints, featureLiteral, node); }); + //for loop constraints + for (IConstraint constraint : featureModel.getConstraints()) { + constraints.add(constraint.getFormula()); + } + Reference reference = new Reference(new And(constraints)); reference.setFreeVariables(variables); return Result.of(reference); diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index c5f83d18..c705bea4 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -26,8 +26,12 @@ import de.featjar.base.computation.Computations; import de.featjar.base.computation.IComputation; import de.featjar.base.data.Result; +import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.io.IO; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureModel; +import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.ComputeDistributionFeatureSelections; import de.featjar.feature.model.analysis.ComputeFeatureCounter; import de.featjar.feature.model.analysis.ComputeNumberConfigurations; @@ -127,7 +131,8 @@ public void computeNumberVariablesTest() { @Test public void computeUniformity() { - FeatJAR.initialize(); + FeatJAR.initialize(); + /* if (Files.exists(Paths.get("../formula/src/testFixtures/resources/Automotive02_V1"))) { System.out.println("The file exists."); } else { @@ -137,6 +142,44 @@ public void computeUniformity() { Result featureModelFormatResult = Result.empty(); featureModelFormatResult.of(IO.load(Paths.get("../formula/src/testFixtures/resources/Automotive02_V1") , FeatureModelFormats.getInstance()).get()); - System.out.println(featureModelFormatResult.get().getNumberOfConstraints()); + System.out.println(featureModelFormatResult.get().getNumberOfConstraints());*/ + IComputation> computation = Computations.of(IO.load(Paths.get("../formula/src/testFixtures/resources/testFeatureModels/basic.xml"), + FeatureModelFormats.getInstance()).get()).map(ComputeUniformity::new); + + //IComputation> computation = Computations.of(()).map(ComputeUniformity::new); + HashMap result = computation.compute(); + System.out.println(result); + } + + private IFeatureTree generateMediumTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree treeRoot = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); + + IFeature featureAPI = featureModel.mutate().addFeature("API"); + IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); + treeAPI.isMandatory(); + IFeature featureGet = featureModel.mutate().addFeature("Get"); + treeAPI.mutate().addFeatureBelow(featureGet); + IFeature featurePut = featureModel.mutate().addFeature("Put"); + treeAPI.mutate().addFeatureBelow(featurePut); + IFeature featureDelete = featureModel.mutate().addFeature("Delete"); + treeAPI.mutate().addFeatureBelow(featureDelete); + treeAPI.mutate().toOrGroup(); + + IFeature featureOS = featureModel.mutate().addFeature("OS"); + IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); + treeOS.isMandatory(); + IFeature featureWindows = featureModel.mutate().addFeature("Windows"); + treeOS.mutate().addFeatureBelow(featureWindows); + IFeature featureLinux = featureModel.mutate().addFeature("Linux"); + treeOS.mutate().addFeatureBelow(featureLinux); + treeOS.mutate().toAlternativeGroup(); + + IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); + IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); + treeTransactions.isOptional(); + + return treeRoot; } } From f00e83f633f97f5200bc75580574364bf1b81dd7 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 14 Oct 2025 16:16:23 +0200 Subject: [PATCH 163/257] feat: testing reading/writing BooleanAssignmentList files WIP --- .../cli/ConfigurationFormatConversion.java | 111 ++++++++++++------ .../binaryBooleanAssignmentList.bin | Bin 0 -> 59 bytes .../csvBooleanAssignmentList.csv | 10 ++ .../dimacsBooleanAssignmentList.dimacs | 10 ++ .../BooleanAssignmentLists/sample.csv | 7 ++ 5 files changed, 104 insertions(+), 34 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/binaryBooleanAssignmentList.bin create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/csvBooleanAssignmentList.csv create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/dimacsBooleanAssignmentList.dimacs create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/sample.csv diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 903cf35a..db5652a1 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -27,13 +27,23 @@ import de.featjar.base.data.Result; import de.featjar.base.io.IO; import de.featjar.base.io.format.IFormat; +import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.formula.assignment.BooleanAssignmentList; +import de.featjar.formula.io.BooleanAssignmentListFormats; +import de.featjar.formula.io.IBooleanAssignmentListFormat; +import de.featjar.formula.io.binary.BooleanAssignmentListBinaryFormat; +import de.featjar.formula.io.csv.BooleanAssignmentListCSVFormat; +import de.featjar.formula.io.dimacs.BooleanAssignmentListDimacsFormat; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.nio.file.Paths; import java.util.EnumMap; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Objects; @@ -43,7 +53,7 @@ /** * Prints statistics about a provided Feature Model. * - * @author Knut, Kilian & Benjamin + * @author Knut & Kilian */ // BooleanAssignmentValueMapFormat implements IFormat @@ -51,13 +61,13 @@ public class ConfigurationFormatConversion implements ICommand { private static final List supportedInputFileExtensions = - FeatureModelFormats.getInstance().getExtensions().stream() + BooleanAssignmentListFormats.getInstance().getExtensions().stream() .filter(IFormat::supportsParse) .map(IFormat::getFileExtension) .collect(Collectors.toList()); private static final List supportedOutputFileExtensions = - FeatureModelFormats.getInstance().getExtensions().stream() + BooleanAssignmentListFormats.getInstance().getExtensions().stream() .filter(IFormat::supportsWrite) .map(IFormat::getFileExtension) .collect(Collectors.toList()); @@ -137,31 +147,63 @@ public String toString() { @Override public int run(OptionList optionParser) { - if (!checkIfInputOutputIsPresent(optionParser)) { - return 1; - } - Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); - - // check if provided file extensions are supported - String inputFileExtension = - IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get()); - String outputFileExtension = - IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); - if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { - return 2; - } - - // informing user about information loss during conversion between file formats - infoLossMessage(inputFileExtension, outputFileExtension); - - // check if model was corrected extracted from input - IFeatureModel model = inputParser(optionParser); - if (model == null) { - FeatJAR.log().error("No model parsed from input file!"); - return 3; + /* + Possible IBooleanAssignmentListFormats: + BooleanAssignmentListBinaryFormat + BooleanAssignmentListCSVFormat + BooleanAssignmentListDimacsFormat + BooleanAssignmentListSimpleTextFormat + BooleanAssignmentListTextFormat + */ + + Path path = Paths.get("src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/sample.csv"); + Result load = IO.load(path, BooleanAssignmentListFormats.getInstance()); + System.out.println(load.get()); + + try { + IO.save(load.get(), optionParser.getResult(OUTPUT_OPTION).orElseThrow(), new BooleanAssignmentListCSVFormat()); + } catch (Exception e) { + FeatJAR.log().error(e); } - - return saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE)); +// +// +// + + +// +// if (!checkIfInputOutputIsPresent(optionParser)) { +// return 1; +// } +// Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); +// +// // check if provided file extensions are supported +// String inputFileExtension = +// IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get()); +// String outputFileExtension = +// IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); +// if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { +// return 2; +// } +// +// System.out.println(Files.exists(optionParser.getResult(INPUT_OPTION).orElseThrow())); +// +// // informing user about information loss during conversion between file formats +// //infoLossMessage(inputFileExtension, outputFileExtension); +// +// // check if model was corrected extracted from input +// Result load = inputParser(optionParser); +// if (load == null) { +// FeatJAR.log().error("No model parsed from input file!"); +// return 3; +// } +//// try { +//// IO.save(load.get(), optionParser.getResult(OUTPUT_OPTION).orElseThrow(), new BooleanAssignmentListCSVFormat()); +//// +//// } catch (Exception e) { +//// FeatJAR.log().error(e); +//// } + + return 0 /*saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE))*/; } /** @@ -325,16 +367,17 @@ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { * @param optionParser holds the command line parameters * @return Feature Model read out from input file. Will be null on failure. */ - private IFeatureModel inputParser(OptionList optionParser) { - Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); - IFeatureModel model = null; - try { - Result load = IO.load(inputPath, FeatureModelFormats.getInstance()); - model = load.get(); + private Result inputParser(OptionList optionParser) { + Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); + Result load = null; + try { + load = IO.load(inputPath, BooleanAssignmentListFormats.getInstance()); + //load.get(); + } catch (Exception e) { FeatJAR.log().error(e.getMessage()); } - return model; + return load; } /** diff --git a/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/binaryBooleanAssignmentList.bin b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/binaryBooleanAssignmentList.bin new file mode 100644 index 0000000000000000000000000000000000000000..ca3d0b11c7027abf18fe1af9897787b76d909153 GIT binary patch literal 59 ocmZQzU|?ooU|=)^5k?@w7)Y=&f>^d-(i}{RgGo~`DGMg$0WdBBs{jB1 literal 0 HcmV?d00001 diff --git a/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/csvBooleanAssignmentList.csv b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/csvBooleanAssignmentList.csv new file mode 100644 index 00000000..abf00646 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/csvBooleanAssignmentList.csv @@ -0,0 +1,10 @@ +c 1 1 +c 2 2 +c 3 3 +p cnf 3 6 +-1 2 3 0 +1 -2 3 0 +1 -2 -3 0 +-1 -2 3 0 +-1 2 -3 0 +1 2 -3 0 diff --git a/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/dimacsBooleanAssignmentList.dimacs b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/dimacsBooleanAssignmentList.dimacs new file mode 100644 index 00000000..abf00646 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/dimacsBooleanAssignmentList.dimacs @@ -0,0 +1,10 @@ +c 1 1 +c 2 2 +c 3 3 +p cnf 3 6 +-1 2 3 0 +1 -2 3 0 +1 -2 -3 0 +-1 -2 3 0 +-1 2 -3 0 +1 2 -3 0 diff --git a/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/sample.csv b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/sample.csv new file mode 100644 index 00000000..c9d39cee --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/sample.csv @@ -0,0 +1,7 @@ +Configuration;1;2;3 +0;-;+;+ +1;+;-;+ +2;+;-;- +3;-;-;+ +4;-;+;- +5;+;+;- From 4e5bdb6c8b56f7d58627d8d60b56c66bce24fca0 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 14 Oct 2025 16:45:16 +0200 Subject: [PATCH 164/257] feat: Analysis Tree now integrated into chart building pipeline + built first chart to display group distribution --- .../AVisualizeFeatureModelStats.java | 63 ++++++++++- .../model/analysis/visualization/Testtmp.java | 101 ++++++++++++++++++ ...isualizeFeatureConstraintDistribution.java | 23 ---- .../VisualizeFeatureGroupDistribution.java | 47 +++++++- .../VisualizeFeatureTypeDistribution.java | 24 ----- .../feature/model/cli/PrintStatistics.java | 5 +- .../computation/ComputeAverageConstraint.java | 5 +- 7 files changed, 214 insertions(+), 54 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java delete mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java delete mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 69f39edf..bd3a431f 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -1,9 +1,15 @@ package de.featjar.feature.model.analysis.visualization; +import de.featjar.base.FeatJAR; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; import org.knowm.xchart.SwingWrapper; import org.knowm.xchart.internal.chartpart.Chart; +import java.util.*; + /** * A class that builds a visualization using the XChart library. * Data is read as an {@link AnalysisTree} and the chart is built by the buildChart() methods of each child class. @@ -13,7 +19,8 @@ */ public abstract class AVisualizeFeatureModelStats { - final private AnalysisTree analysisTree; + final protected AnalysisTree analysisTree; + protected HashMap analysisTreeData = null; private Chart chart; private String chartTitle = "Chart"; @@ -23,6 +30,11 @@ public abstract class AVisualizeFeatureModelStats { public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { this.analysisTree = analysisTree; + try { + this.extractAnalysisTree(); + } catch (Exception e) { + System.out.println(e); + } this.chart = buildChart(); } @@ -53,7 +65,54 @@ public void setHeight(Integer height) { /** * - * {@return the chart that will be used by the other class methods} + * {@return String key used to fetch data from the Analysis Tree later.} + */ + protected abstract String getAnalysisTreeDataName(); + + /** + * uses internal analysisTree and getAnalysisTreeDataName() to fetch the needed piece of data. + */ + public void extractAnalysisTree() throws RuntimeException { + // traverse tree to extract the general HashMap that more specific information is stored in. + AnalysisTreeVisitor visitor = new AnalysisTreeVisitor(); + Result> result = Trees.traverse(analysisTree, visitor); + HashMap receivedResult = result.get(); + assert receivedResult != null: "Analysis Tree Visitor failed to produce a result."; + @SuppressWarnings("unchecked") + HashMap analysisMap = (HashMap) receivedResult.get("Analysis"); + assert analysisMap != null: "Received no \"Analysis\" HashMap from AnalysisTree"; + + // build keys for each tree + String statName = this.getAnalysisTreeDataName(); + Set analysisMapKeys = analysisMap.keySet(); + ArrayList relevantKeys = new ArrayList<>(); + for (String key : analysisMapKeys) { + if (key.contains(statName)) { + relevantKeys.add(key); + } + } + + // for each tree + HashMap analysisTreeData = new HashMap<>(); + for (String key : relevantKeys) { + Object attributeResult = analysisMap.get(key); + assert attributeResult != null : "Could not retrieve data called " + key + " from AnalysisTree."; + + if (attributeResult instanceof Map) { + analysisTreeData.put(key, attributeResult); + } else if (attributeResult instanceof ArrayList) { + analysisTreeData.put(key, ((ArrayList) attributeResult).get(2)); + } else { + throw new RuntimeException("Analysis Tree contained unknown data type in key " + key); + } + } + + this.analysisTreeData = analysisTreeData; + } + + /** + * You can use the analysisTreeData array list to access the analysisTree data relevant for building your chart. + * @return the chart that will be used by the other class methods */ abstract Chart buildChart(); diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java new file mode 100644 index 00000000..3b8c5426 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java @@ -0,0 +1,101 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeature; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.cli.PrintStatistics; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; + +import java.util.LinkedHashMap; + +public class Testtmp { + + public static AnalysisTree createDefaultTree() { + AnalysisTree innereanalysisTree = new AnalysisTree<>( + "avgNumOfAtomsPerConstraints", + new AnalysisTree<>("test property", 3.3), + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); + + AnalysisTree analysisTree = new AnalysisTree<>( + "Analysis", + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), + new AnalysisTree<>("numOfTopFeatures", 3.3), + new AnalysisTree<>("treeDepth", 3), + new AnalysisTree<>("avgNumOfChildren", 3), + new AnalysisTree<>("numInOrGroups", 7), + new AnalysisTree<>("numInAltGroups", 5), + new AnalysisTree<>("numOfAtoms", 8), + new AnalysisTree<>("Group Distribution", 4), + innereanalysisTree); + return analysisTree; + } + + public static AnalysisTree generateEmptyTree() { + FeatureModel emptyFeatureModel = new FeatureModel(); + emptyFeatureModel.mutate().addFeatureTreeRoot(emptyFeatureModel.mutate().addFeature("root")); + + PrintStatistics printStatistics = new PrintStatistics(); + LinkedHashMap map = printStatistics.collectStats( + generateMediumTree(), + PrintStatistics.AnalysesScope.ALL + ); + + return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + } + + /** + * Feature tree with three nodes under the root. API is mandatory and below it is an or-group with the features + * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. + * Transactions is an optional feature below the root. + * @return a medium-sized feature tree for testing purposes. + */ + public static FeatureModel generateMediumTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree treeRoot = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); + + IFeature featureAPI = featureModel.mutate().addFeature("API"); + IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); + treeAPI.isMandatory(); + IFeature featureGet = featureModel.mutate().addFeature("Get"); + treeAPI.mutate().addFeatureBelow(featureGet); + IFeature featurePut = featureModel.mutate().addFeature("Put"); + treeAPI.mutate().addFeatureBelow(featurePut); + IFeature featureDelete = featureModel.mutate().addFeature("Delete"); + treeAPI.mutate().addFeatureBelow(featureDelete); + treeAPI.mutate().toOrGroup(); + + IFeature featureOS = featureModel.mutate().addFeature("OS"); + IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); + treeOS.isMandatory(); + IFeature featureWindows = featureModel.mutate().addFeature("Windows"); + treeOS.mutate().addFeatureBelow(featureWindows); + IFeature featureLinux = featureModel.mutate().addFeature("Linux"); + treeOS.mutate().addFeatureBelow(featureLinux); + treeOS.mutate().toAlternativeGroup(); + + IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); + IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); + treeTransactions.isOptional(); + + return featureModel; + } + + public static void main(String[] args) { + System.out.println("Hello"); + PrintStatistics printStatistics = new PrintStatistics(); + LinkedHashMap map = printStatistics.collectStats( + generateMediumTree(), + PrintStatistics.AnalysesScope.ALL + ); + AnalysisTree analysisTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + + VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(analysisTree); + //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(createDefaultTree()); + //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(generateEmptyTree()); + viz.displayChart(); + + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java deleted file mode 100644 index 9b15bac6..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.featjar.feature.model.analysis.visualization; - -import de.featjar.feature.model.analysis.AnalysisTree; -import org.knowm.xchart.internal.chartpart.Chart; - -public class VisualizeFeatureConstraintDistribution extends AVisualizeFeatureModelStats{ - public VisualizeFeatureConstraintDistribution(AnalysisTree analysisTree) { - super(analysisTree); - } - - public VisualizeFeatureConstraintDistribution(AnalysisTree analysisTree, String chartTitle) { - super(analysisTree, chartTitle); - } - - /** - * - * {@return the chart that will be used by the other class methods} - */ - @Override - Chart buildChart() { - return null; - } -} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java index 4abdda41..31df2272 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java @@ -1,8 +1,15 @@ package de.featjar.feature.model.analysis.visualization; import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.PieChart; +import org.knowm.xchart.PieChartBuilder; +import org.knowm.xchart.QuickChart; import org.knowm.xchart.internal.chartpart.Chart; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Set; + public class VisualizeFeatureGroupDistribution extends AVisualizeFeatureModelStats{ public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree) { super(analysisTree); @@ -14,10 +21,46 @@ public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree, String ch /** * - * {@return the chart that will be used by the other class methods} + * {@return String key used to fetch data from the Analysis Tree later.} + */ + @Override + protected String getAnalysisTreeDataName() { + return "Group Distribution"; + } + + /** + * You can use the analysisTreeData array list to access the analysisTree data relevant for building your chart. + * @return the chart that will be used by the other class methods */ @Override Chart buildChart() { - return null; + + PieChart chart1 = new PieChartBuilder().build(); + + // todo sort keys for trees and in alphabetical order + for (String key : this.analysisTreeData.keySet()) { // für jeden Baum + @SuppressWarnings("unchecked") + HashMap nestedMap = (HashMap) this.analysisTreeData.get(key); + Set groupDistributionKeys = nestedMap.keySet(); + + for (String groupKey : groupDistributionKeys) { + ArrayList groupResult = (ArrayList) nestedMap.get(groupKey); + chart1.addSeries(groupKey, (Integer) groupResult.get(2)); + } + } + + // placeholder + /* + PieChart chart1 = new PieChartBuilder().build(); + chart1.addSeries("Banananana", 40); + chart1.addSeries("Apfel", 25); + chart1.addSeries("Gurke", 50); + + */ + + // data that we actually want to use + // System.out.println(this.analysisTreeData); + + return chart1; } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java deleted file mode 100644 index d46fa4ec..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.featjar.feature.model.analysis.visualization; - -import de.featjar.feature.model.analysis.AnalysisTree; -import org.knowm.xchart.internal.chartpart.Chart; - -public class VisualizeFeatureTypeDistribution extends AVisualizeFeatureModelStats{ - public VisualizeFeatureTypeDistribution(AnalysisTree analysisTree) { - super(analysisTree); - } - - public VisualizeFeatureTypeDistribution(AnalysisTree analysisTree, String chartTitle) { - super(analysisTree, chartTitle); - } - - /** - * - * {@return the chart that will be used by the other class methods} - */ - @Override - Chart buildChart() { - return null; - } - -} diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index f10776be..161e8ce1 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -170,7 +170,10 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc HashMap computational_opDensity = Computations.of(model).map(ComputeOperatorDistribution::new).compute(); - data.put("Operator Distribution", computational_opDensity); + //data.put("Operator Distribution", computational_opDensity); + if (computational_opDensity.size() != 0) { + data.put("Operator Distribution", computational_opDensity); + } } if ((scope == AnalysesScope.ALL || scope == AnalysesScope.TREE_RELATED)) { diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 34a5ff9b..56a04ec4 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -75,7 +75,8 @@ public Result compute(List dependencyList, Progress progress) { COUNTBOOLEAN.get(dependencyList))) .orElse(0); } - return Result.of( - (float) atomsSum / (float) featureModel.getConstraints().size()); + int numberOfConstraints = featureModel.getConstraints().size(); + float averageConstraint = (numberOfConstraints == 0)? 0 : (float) atomsSum / (float) numberOfConstraints; + return Result.of(averageConstraint); } } From 399ed2ba014cf1a38fa7394a8ce72304685ae28e Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 14 Oct 2025 16:48:29 +0200 Subject: [PATCH 165/257] doc: added small todo reminder for ourselves --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index bd3a431f..663ba1c9 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -99,6 +99,7 @@ public void extractAnalysisTree() throws RuntimeException { assert attributeResult != null : "Could not retrieve data called " + key + " from AnalysisTree."; if (attributeResult instanceof Map) { + // todo instead of transferring the whole map, only transfer its get(2) value analysisTreeData.put(key, attributeResult); } else if (attributeResult instanceof ArrayList) { analysisTreeData.put(key, ((ArrayList) attributeResult).get(2)); From 9e808a8e9485a556d7ba24fea44f9c532b0220e2 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Tue, 14 Oct 2025 16:51:17 +0200 Subject: [PATCH 166/257] ConfigurationFormat story card started --- .../cli/ConfigurationFormatConversion.java | 102 +++++++++++------- ...mentList.bin => BooleanAssignmentList.bin} | Bin .../{sample.csv => BooleanAssignmentList.csv} | 0 ...tList.csv => BooleanAssignmentList.dimacs} | 0 .../dimacsBooleanAssignmentList.dimacs | 10 -- 5 files changed, 64 insertions(+), 48 deletions(-) rename src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/{binaryBooleanAssignmentList.bin => BooleanAssignmentList.bin} (100%) rename src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/{sample.csv => BooleanAssignmentList.csv} (100%) rename src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/{csvBooleanAssignmentList.csv => BooleanAssignmentList.dimacs} (100%) delete mode 100644 src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/dimacsBooleanAssignmentList.dimacs diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index db5652a1..6413d0cc 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -155,16 +155,27 @@ public int run(OptionList optionParser) { BooleanAssignmentListSimpleTextFormat BooleanAssignmentListTextFormat */ + + String outputFileExtension = + IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); + - Path path = Paths.get("src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/sample.csv"); - Result load = IO.load(path, BooleanAssignmentListFormats.getInstance()); - System.out.println(load.get()); + IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).orElseThrow()); + Result load = IO.load(optionParser.getResult(INPUT_OPTION).orElseThrow(), BooleanAssignmentListFormats.getInstance()); + + Optional> outputFormats = BooleanAssignmentListFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) + .findFirst(); try { - IO.save(load.get(), optionParser.getResult(OUTPUT_OPTION).orElseThrow(), new BooleanAssignmentListCSVFormat()); + IO.save(load.get(), optionParser.getResult(OUTPUT_OPTION).orElseThrow(), outputFormats.get()); } catch (Exception e) { - FeatJAR.log().error(e); + System.out.println(e); } + + saveFile(optionParser.getResult(OUTPUT_OPTION).orElseThrow(), outputFileExtension, optionParser.get(OVERWRITE)); + // // // @@ -391,39 +402,54 @@ private Result inputParser(OptionList optionParser) { * 4 if a file is already present at output path and no overwrite is specified * 5 on IOException */ - public int saveFile(Path outputPath, IFeatureModel model, String outputFileExtension, boolean overWriteOutputFile) { - - IFormat format; - - Optional> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() - .filter(IFormat::supportsWrite) - .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) - .findFirst(); - if (outputFormats.isEmpty()) { - FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); - return 2; - } else { - format = outputFormats.get(); - } - - try { - if (Files.exists(outputPath)) { - if (overWriteOutputFile) { - FeatJAR.log().info("File already present at: " + outputPath + ". Continuing to overwrite File."); - } else { - FeatJAR.log() - .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath - + ". To overwrite present file add --overwrite"); - return 4; - } - } - IO.save(model, outputPath, format); - - } catch (IOException e) { - FeatJAR.log().error(e); - return 5; - } - FeatJAR.log().message("Output model saved at: " + outputPath); + public int saveFile(Path outputPath, String outputFileExtension, boolean overWriteOutputFile) { + + + IO.getFileExtension(outputPath); + + Result load = IO.load(outputPath, BooleanAssignmentListFormats.getInstance()); + + Optional> outputFormats = BooleanAssignmentListFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) + .findFirst(); + try { + IO.save(load.get(), outputPath, outputFormats.get()); + } catch (Exception e) { + System.out.println(e); + } + +// IFormat format; +// +// Optional> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() +// .filter(IFormat::supportsWrite) +// .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) +// .findFirst(); +// if (outputFormats.isEmpty()) { +// FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); +// return 2; +// } else { +// format = outputFormats.get(); +// } +// +// try { +// if (Files.exists(outputPath)) { +// if (overWriteOutputFile) { +// FeatJAR.log().info("File already present at: " + outputPath + ". Continuing to overwrite File."); +// } else { +// FeatJAR.log() +// .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath +// + ". To overwrite present file add --overwrite"); +// return 4; +// } +// } +// IO.save(model, outputPath, format); +// +// } catch (IOException e) { +// FeatJAR.log().error(e); +// return 5; +// } +// FeatJAR.log().message("Output model saved at: " + outputPath); return 0; } diff --git a/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/binaryBooleanAssignmentList.bin b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.bin similarity index 100% rename from src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/binaryBooleanAssignmentList.bin rename to src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.bin diff --git a/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/sample.csv b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.csv similarity index 100% rename from src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/sample.csv rename to src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.csv diff --git a/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/csvBooleanAssignmentList.csv b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.dimacs similarity index 100% rename from src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/csvBooleanAssignmentList.csv rename to src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.dimacs diff --git a/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/dimacsBooleanAssignmentList.dimacs b/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/dimacsBooleanAssignmentList.dimacs deleted file mode 100644 index abf00646..00000000 --- a/src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/dimacsBooleanAssignmentList.dimacs +++ /dev/null @@ -1,10 +0,0 @@ -c 1 1 -c 2 2 -c 3 3 -p cnf 3 6 --1 2 3 0 -1 -2 3 0 -1 -2 -3 0 --1 -2 3 0 --1 2 -3 0 -1 2 -3 0 From b76e55a69e8019c8c69b0f76d5c2fe6f3e84f4c7 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Tue, 14 Oct 2025 16:52:13 +0200 Subject: [PATCH 167/257] feat: continued the development of ComputeUniformity. --- .../model/analysis/ComputeUniformity.java | 46 +++++++++++++++++-- .../model/analysis/SamplePropertiesTest.java | 18 +++++++- 2 files changed, 60 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java index 4dc307d3..bc2e5ff8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -2,7 +2,9 @@ import java.math.BigInteger; import java.util.HashMap; +import java.util.LinkedList; import java.util.List; +import java.util.Map.Entry; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Computations; @@ -13,6 +15,7 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.transformer.ComputeFormula; import de.featjar.formula.VariableMap; +import de.featjar.formula.assignment.BooleanAssignment; import de.featjar.formula.assignment.BooleanAssignmentList; import de.featjar.formula.computation.ComputeCNFFormula; import de.featjar.formula.computation.ComputeNNFFormula; @@ -22,6 +25,7 @@ import de.featjar.formula.structure.predicate.Literal; import de.featjar.formula.structure.term.value.Variable; import de.featjar.feature.model.IFeatureModel; +import de.featjar.analysis.javasmt.computation.ComputeSatisfiability; import de.featjar.analysis.javasmt.computation.ComputeSolutionCount; public class ComputeUniformity extends AComputation> { @@ -37,7 +41,7 @@ public ComputeUniformity(IComputation featureModel) { @Override public Result> compute(List dependencyList, Progress progress) { - BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + BooleanAssignmentList booleanAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); IFeatureModel featureModel = FEATURE_MODEL.get(dependencyList); IComputation iFormula = Computations.of(featureModel).map(ComputeFormula::new); IFormula fmFormula = iFormula.compute(); @@ -48,17 +52,53 @@ public Result> compute(List dependencyList, Progr BigInteger solutionsCount = solutionCountComputation.compute(); HashMap returnMap = new HashMap(); HashMap fmMap = new HashMap(); + HashMap sampleMap = new HashMap(); + VariableMap fmVariableMap = new VariableMap(fmFormula); + + for (String varName : fmVariableMap.getVariableNames()) { + fmMap.put(varName, (float) 0); + sampleMap.put(varName, (float) 0); + } + fmMap.put("all", solutionsCount.floatValue()); - for (String varName : new VariableMap(fmFormula).getVariableNames()) { + for (String varName : fmVariableMap.getVariableNames()) { Reference currentFormula = new Reference(new And((IFormula)fmFormula.getChildren().get(0), new Literal(varName))); currentFormula.setFreeVariables(((Reference)fmFormula).getFreeVariables()); - System.out.println(currentFormula.print()); IFormula NNFFormula = Computations.of((IFormula)currentFormula).map(ComputeNNFFormula::new).compute(); fmMap.put(varName, Computations.of(NNFFormula) .map(ComputeCNFFormula::new).map(ComputeSolutionCount::new).compute().floatValue()); + } + + int assignmentSolutionsCount = 0; + for(BooleanAssignment booleanAssignment : booleanAssignmentList.getAll()) { + IFormula currentIFormulaAssignment = new And(); + List currentAssignmentVariables = new LinkedList(); + for (int index : booleanAssignment.get()) { + if(fmVariableMap.get(index).isPresent()) { + currentIFormulaAssignment = new And(currentIFormulaAssignment, new Literal(fmVariableMap.get(index).orElseThrow())); + currentAssignmentVariables.add(fmVariableMap.get(index).get()); + } + } + Reference currentFormula = new Reference(new And((IFormula)fmFormula.getChildren().get(0), currentIFormulaAssignment)); + System.out.println(fmFormula.print()); + System.out.println("Assignment: " + booleanAssignment + "\n" + currentFormula.print()); + System.out.println(Computations.of((IFormula)currentFormula).map(ComputeNNFFormula::new) + .map(ComputeCNFFormula::new).map(ComputeSolutionCount::new).compute()); + currentFormula.setFreeVariables(((Reference)fmFormula).getFreeVariables()); + if(Computations.of((IFormula)currentFormula).map(ComputeNNFFormula::new) + .map(ComputeCNFFormula::new).map(ComputeSolutionCount::new).compute().intValue() > 0) { + assignmentSolutionsCount++; + for (String key : currentAssignmentVariables) { + sampleMap.replace(key, sampleMap.get(key) + 1); + } + } } + + System.out.println("sampleMap: \n" + sampleMap); + System.out.println("assignmentSolutionsCount: " + assignmentSolutionsCount); + System.out.println("solutionsCount: " + solutionsCount); return Result.of(fmMap); } } \ No newline at end of file diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index c705bea4..993dafe7 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -69,6 +69,22 @@ public BooleanAssignmentList createAssignmentList() { new BooleanAssignment()); return booleanAssignmentList; } + + public BooleanAssignmentList createAssignmentListUniformity() { + LinkedList variableNames = new LinkedList(); + variableNames.add("Root"); + variableNames.add("A"); + variableNames.add("B"); + VariableMap variableMap = new VariableMap(variableNames); + + BooleanAssignmentList booleanAssignmentList = new BooleanAssignmentList( + variableMap, + new BooleanAssignment(2), + new BooleanAssignment(3), + new BooleanAssignment(-2), + new BooleanAssignment(-3)); + return booleanAssignmentList; + } @Test public void computeDistributionFeaturesSelectionsTest() { @@ -144,7 +160,7 @@ public void computeUniformity() { , FeatureModelFormats.getInstance()).get()); System.out.println(featureModelFormatResult.get().getNumberOfConstraints());*/ IComputation> computation = Computations.of(IO.load(Paths.get("../formula/src/testFixtures/resources/testFeatureModels/basic.xml"), - FeatureModelFormats.getInstance()).get()).map(ComputeUniformity::new); + FeatureModelFormats.getInstance()).get()).map(ComputeUniformity::new).set(ComputeUniformity.BOOLEAN_ASSIGMENT_LIST, createAssignmentListUniformity()); //IComputation> computation = Computations.of(()).map(ComputeUniformity::new); HashMap result = computation.compute(); From 8e53e132044b4c1d9b1fcfeaf0e9f239db8faad5 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 13 Oct 2025 15:32:34 +0200 Subject: [PATCH 168/257] chore: added XChart dependency to gradle --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index 45fa0bb9..689ec301 100644 --- a/build.gradle +++ b/build.gradle @@ -9,6 +9,7 @@ dependencies { implementation 'org.json:json:20240303' implementation 'org.yaml:snakeyaml:2.5' implementation 'tools.jackson.dataformat:jackson-dataformat-csv:3.0.0' + implementation 'org.knowm.xchart:xchart:3.8.8' } license { From 0961e25ac256b677885b6c2d7c88cd13bb40fefc Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Mon, 13 Oct 2025 16:40:26 +0200 Subject: [PATCH 169/257] feat prototype visualization --- .../VisualizeFeatureModelStats.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java new file mode 100644 index 00000000..1c280dbb --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java @@ -0,0 +1,59 @@ +package de.featjar.feature.model.analysis.visualization; + +import org.knowm.xchart.QuickChart; +import org.knowm.xchart.SwingWrapper; +import org.knowm.xchart.XYChart; +import org.knowm.xchart.PieChart; +import org.knowm.xchart.PieChartBuilder; + +public class VisualizeFeatureModelStats { + + private double[] xData; + private double[] yData; + + private String chartTitle; + private String xTitle; + private String yTitle; + private String seriesName = "y(x)"; + + public VisualizeFeatureModelStats(double[] xData, double[] yData) { + this.xData = xData; + this.yData = yData; + this.chartTitle = "Titel"; + this.xTitle = "x-Achse"; + this.yTitle = "y-Achse"; + } + + public VisualizeFeatureModelStats(double[] xData, double[] yData, String chartTitle, String xTitle, String yTitle) { + this.xData = xData; + this.yData = yData; + this.chartTitle = chartTitle; + this.xTitle = xTitle; + this.yTitle = yTitle; + } + + public XYChart getChart() { + return QuickChart.getChart + (this.chartTitle, this.xTitle, this.yTitle, this.seriesName, this.xData, this.yData); + } + + public void displayChart() { + //new Swing + } + public static void main(String[] args) { + // Sample data + double[] xData = new double[] {0.0, 1.0, 2.0}; + double[] yData = new double[] {2.0, 1.0, 0.0}; + + // Create Chart + XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData); + PieChart chart1 = new PieChartBuilder().build(); + + chart1.addSeries("Banananana", 40); + chart1.addSeries("Apple", 25); + chart1.addSeries("Gurke", 50); + + // Display the chart + new SwingWrapper<>(chart1).displayChart(); + } +} From cba49d172d091d0803b28272c915bf5618efc4f6 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 14 Oct 2025 10:40:05 +0200 Subject: [PATCH 170/257] feat: Abstract class for visualizer implemented. + stubs for concrete child classes added. --- .gitignore | 3 +- .../AVisualizeFeatureModelStats.java | 70 +++++++++++++++++++ ...isualizeFeatureConstraintDistribution.java | 23 ++++++ .../VisualizeFeatureGroupDistribution.java | 23 ++++++ .../VisualizeFeatureModelStats.java | 59 ---------------- .../VisualizeFeatureTypeDistribution.java | 24 +++++++ 6 files changed, 142 insertions(+), 60 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java delete mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java diff --git a/.gitignore b/.gitignore index 09049555..b76fe34e 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ /.classpath /.idea/ /.settings/ -/bin/ \ No newline at end of file +/bin/ +/model_invalidInput.dot diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java new file mode 100644 index 00000000..69f39edf --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -0,0 +1,70 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.SwingWrapper; +import org.knowm.xchart.internal.chartpart.Chart; + +/** + * A class that builds a visualization using the XChart library. + * Data is read as an {@link AnalysisTree} and the chart is built by the buildChart() methods of each child class. + * + * @author Benjamin von Holt + * @author Valentin Laubsch + */ +public abstract class AVisualizeFeatureModelStats { + + final private AnalysisTree analysisTree; + private Chart chart; + + private String chartTitle = "Chart"; + private Integer width; + private Integer height; + + + public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { + this.analysisTree = analysisTree; + this.chart = buildChart(); + } + + public AVisualizeFeatureModelStats(AnalysisTree analysisTree, String chartTitle) { + this(analysisTree); + this.chartTitle = chartTitle; + } + + public Chart getChart() { + return this.chart; + } + + public Integer getWidth() { + return this.width; + } + + public void setWidth(Integer width) { + this.width = width; + } + + public Integer getHeight() { + return this.height; + } + + public void setHeight(Integer height) { + this.height = height; + } + + /** + * + * {@return the chart that will be used by the other class methods} + */ + abstract Chart buildChart(); + + + /** + * Creates a live preview pop-up window of the internally generated chart. + */ + public void displayChart() { + new SwingWrapper<>(this.chart).displayChart(); + } + + // TODO pdf export hinzufügen + +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java new file mode 100644 index 00000000..9b15bac6 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java @@ -0,0 +1,23 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.internal.chartpart.Chart; + +public class VisualizeFeatureConstraintDistribution extends AVisualizeFeatureModelStats{ + public VisualizeFeatureConstraintDistribution(AnalysisTree analysisTree) { + super(analysisTree); + } + + public VisualizeFeatureConstraintDistribution(AnalysisTree analysisTree, String chartTitle) { + super(analysisTree, chartTitle); + } + + /** + * + * {@return the chart that will be used by the other class methods} + */ + @Override + Chart buildChart() { + return null; + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java new file mode 100644 index 00000000..4abdda41 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java @@ -0,0 +1,23 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.internal.chartpart.Chart; + +public class VisualizeFeatureGroupDistribution extends AVisualizeFeatureModelStats{ + public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree) { + super(analysisTree); + } + + public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree, String chartTitle) { + super(analysisTree, chartTitle); + } + + /** + * + * {@return the chart that will be used by the other class methods} + */ + @Override + Chart buildChart() { + return null; + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java deleted file mode 100644 index 1c280dbb..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureModelStats.java +++ /dev/null @@ -1,59 +0,0 @@ -package de.featjar.feature.model.analysis.visualization; - -import org.knowm.xchart.QuickChart; -import org.knowm.xchart.SwingWrapper; -import org.knowm.xchart.XYChart; -import org.knowm.xchart.PieChart; -import org.knowm.xchart.PieChartBuilder; - -public class VisualizeFeatureModelStats { - - private double[] xData; - private double[] yData; - - private String chartTitle; - private String xTitle; - private String yTitle; - private String seriesName = "y(x)"; - - public VisualizeFeatureModelStats(double[] xData, double[] yData) { - this.xData = xData; - this.yData = yData; - this.chartTitle = "Titel"; - this.xTitle = "x-Achse"; - this.yTitle = "y-Achse"; - } - - public VisualizeFeatureModelStats(double[] xData, double[] yData, String chartTitle, String xTitle, String yTitle) { - this.xData = xData; - this.yData = yData; - this.chartTitle = chartTitle; - this.xTitle = xTitle; - this.yTitle = yTitle; - } - - public XYChart getChart() { - return QuickChart.getChart - (this.chartTitle, this.xTitle, this.yTitle, this.seriesName, this.xData, this.yData); - } - - public void displayChart() { - //new Swing - } - public static void main(String[] args) { - // Sample data - double[] xData = new double[] {0.0, 1.0, 2.0}; - double[] yData = new double[] {2.0, 1.0, 0.0}; - - // Create Chart - XYChart chart = QuickChart.getChart("Sample Chart", "X", "Y", "y(x)", xData, yData); - PieChart chart1 = new PieChartBuilder().build(); - - chart1.addSeries("Banananana", 40); - chart1.addSeries("Apple", 25); - chart1.addSeries("Gurke", 50); - - // Display the chart - new SwingWrapper<>(chart1).displayChart(); - } -} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java new file mode 100644 index 00000000..d46fa4ec --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java @@ -0,0 +1,24 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.internal.chartpart.Chart; + +public class VisualizeFeatureTypeDistribution extends AVisualizeFeatureModelStats{ + public VisualizeFeatureTypeDistribution(AnalysisTree analysisTree) { + super(analysisTree); + } + + public VisualizeFeatureTypeDistribution(AnalysisTree analysisTree, String chartTitle) { + super(analysisTree, chartTitle); + } + + /** + * + * {@return the chart that will be used by the other class methods} + */ + @Override + Chart buildChart() { + return null; + } + +} From c0d401f022946de7b44b90951932a19a17c6bf0d Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 14 Oct 2025 16:45:16 +0200 Subject: [PATCH 171/257] feat: Analysis Tree now integrated into chart building pipeline + built first chart to display group distribution --- .../AVisualizeFeatureModelStats.java | 63 ++++++++++- .../model/analysis/visualization/Testtmp.java | 101 ++++++++++++++++++ ...isualizeFeatureConstraintDistribution.java | 23 ---- .../VisualizeFeatureGroupDistribution.java | 47 +++++++- .../VisualizeFeatureTypeDistribution.java | 24 ----- .../feature/model/cli/PrintStatistics.java | 1 + .../computation/ComputeAverageConstraint.java | 5 +- 7 files changed, 211 insertions(+), 53 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java delete mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java delete mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 69f39edf..bd3a431f 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -1,9 +1,15 @@ package de.featjar.feature.model.analysis.visualization; +import de.featjar.base.FeatJAR; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; import org.knowm.xchart.SwingWrapper; import org.knowm.xchart.internal.chartpart.Chart; +import java.util.*; + /** * A class that builds a visualization using the XChart library. * Data is read as an {@link AnalysisTree} and the chart is built by the buildChart() methods of each child class. @@ -13,7 +19,8 @@ */ public abstract class AVisualizeFeatureModelStats { - final private AnalysisTree analysisTree; + final protected AnalysisTree analysisTree; + protected HashMap analysisTreeData = null; private Chart chart; private String chartTitle = "Chart"; @@ -23,6 +30,11 @@ public abstract class AVisualizeFeatureModelStats { public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { this.analysisTree = analysisTree; + try { + this.extractAnalysisTree(); + } catch (Exception e) { + System.out.println(e); + } this.chart = buildChart(); } @@ -53,7 +65,54 @@ public void setHeight(Integer height) { /** * - * {@return the chart that will be used by the other class methods} + * {@return String key used to fetch data from the Analysis Tree later.} + */ + protected abstract String getAnalysisTreeDataName(); + + /** + * uses internal analysisTree and getAnalysisTreeDataName() to fetch the needed piece of data. + */ + public void extractAnalysisTree() throws RuntimeException { + // traverse tree to extract the general HashMap that more specific information is stored in. + AnalysisTreeVisitor visitor = new AnalysisTreeVisitor(); + Result> result = Trees.traverse(analysisTree, visitor); + HashMap receivedResult = result.get(); + assert receivedResult != null: "Analysis Tree Visitor failed to produce a result."; + @SuppressWarnings("unchecked") + HashMap analysisMap = (HashMap) receivedResult.get("Analysis"); + assert analysisMap != null: "Received no \"Analysis\" HashMap from AnalysisTree"; + + // build keys for each tree + String statName = this.getAnalysisTreeDataName(); + Set analysisMapKeys = analysisMap.keySet(); + ArrayList relevantKeys = new ArrayList<>(); + for (String key : analysisMapKeys) { + if (key.contains(statName)) { + relevantKeys.add(key); + } + } + + // for each tree + HashMap analysisTreeData = new HashMap<>(); + for (String key : relevantKeys) { + Object attributeResult = analysisMap.get(key); + assert attributeResult != null : "Could not retrieve data called " + key + " from AnalysisTree."; + + if (attributeResult instanceof Map) { + analysisTreeData.put(key, attributeResult); + } else if (attributeResult instanceof ArrayList) { + analysisTreeData.put(key, ((ArrayList) attributeResult).get(2)); + } else { + throw new RuntimeException("Analysis Tree contained unknown data type in key " + key); + } + } + + this.analysisTreeData = analysisTreeData; + } + + /** + * You can use the analysisTreeData array list to access the analysisTree data relevant for building your chart. + * @return the chart that will be used by the other class methods */ abstract Chart buildChart(); diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java new file mode 100644 index 00000000..3b8c5426 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java @@ -0,0 +1,101 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeature; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.cli.PrintStatistics; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; + +import java.util.LinkedHashMap; + +public class Testtmp { + + public static AnalysisTree createDefaultTree() { + AnalysisTree innereanalysisTree = new AnalysisTree<>( + "avgNumOfAtomsPerConstraints", + new AnalysisTree<>("test property", 3.3), + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); + + AnalysisTree analysisTree = new AnalysisTree<>( + "Analysis", + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), + new AnalysisTree<>("numOfTopFeatures", 3.3), + new AnalysisTree<>("treeDepth", 3), + new AnalysisTree<>("avgNumOfChildren", 3), + new AnalysisTree<>("numInOrGroups", 7), + new AnalysisTree<>("numInAltGroups", 5), + new AnalysisTree<>("numOfAtoms", 8), + new AnalysisTree<>("Group Distribution", 4), + innereanalysisTree); + return analysisTree; + } + + public static AnalysisTree generateEmptyTree() { + FeatureModel emptyFeatureModel = new FeatureModel(); + emptyFeatureModel.mutate().addFeatureTreeRoot(emptyFeatureModel.mutate().addFeature("root")); + + PrintStatistics printStatistics = new PrintStatistics(); + LinkedHashMap map = printStatistics.collectStats( + generateMediumTree(), + PrintStatistics.AnalysesScope.ALL + ); + + return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + } + + /** + * Feature tree with three nodes under the root. API is mandatory and below it is an or-group with the features + * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. + * Transactions is an optional feature below the root. + * @return a medium-sized feature tree for testing purposes. + */ + public static FeatureModel generateMediumTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree treeRoot = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); + + IFeature featureAPI = featureModel.mutate().addFeature("API"); + IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); + treeAPI.isMandatory(); + IFeature featureGet = featureModel.mutate().addFeature("Get"); + treeAPI.mutate().addFeatureBelow(featureGet); + IFeature featurePut = featureModel.mutate().addFeature("Put"); + treeAPI.mutate().addFeatureBelow(featurePut); + IFeature featureDelete = featureModel.mutate().addFeature("Delete"); + treeAPI.mutate().addFeatureBelow(featureDelete); + treeAPI.mutate().toOrGroup(); + + IFeature featureOS = featureModel.mutate().addFeature("OS"); + IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); + treeOS.isMandatory(); + IFeature featureWindows = featureModel.mutate().addFeature("Windows"); + treeOS.mutate().addFeatureBelow(featureWindows); + IFeature featureLinux = featureModel.mutate().addFeature("Linux"); + treeOS.mutate().addFeatureBelow(featureLinux); + treeOS.mutate().toAlternativeGroup(); + + IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); + IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); + treeTransactions.isOptional(); + + return featureModel; + } + + public static void main(String[] args) { + System.out.println("Hello"); + PrintStatistics printStatistics = new PrintStatistics(); + LinkedHashMap map = printStatistics.collectStats( + generateMediumTree(), + PrintStatistics.AnalysesScope.ALL + ); + AnalysisTree analysisTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + + VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(analysisTree); + //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(createDefaultTree()); + //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(generateEmptyTree()); + viz.displayChart(); + + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java deleted file mode 100644 index 9b15bac6..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureConstraintDistribution.java +++ /dev/null @@ -1,23 +0,0 @@ -package de.featjar.feature.model.analysis.visualization; - -import de.featjar.feature.model.analysis.AnalysisTree; -import org.knowm.xchart.internal.chartpart.Chart; - -public class VisualizeFeatureConstraintDistribution extends AVisualizeFeatureModelStats{ - public VisualizeFeatureConstraintDistribution(AnalysisTree analysisTree) { - super(analysisTree); - } - - public VisualizeFeatureConstraintDistribution(AnalysisTree analysisTree, String chartTitle) { - super(analysisTree, chartTitle); - } - - /** - * - * {@return the chart that will be used by the other class methods} - */ - @Override - Chart buildChart() { - return null; - } -} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java index 4abdda41..31df2272 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java @@ -1,8 +1,15 @@ package de.featjar.feature.model.analysis.visualization; import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.PieChart; +import org.knowm.xchart.PieChartBuilder; +import org.knowm.xchart.QuickChart; import org.knowm.xchart.internal.chartpart.Chart; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Set; + public class VisualizeFeatureGroupDistribution extends AVisualizeFeatureModelStats{ public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree) { super(analysisTree); @@ -14,10 +21,46 @@ public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree, String ch /** * - * {@return the chart that will be used by the other class methods} + * {@return String key used to fetch data from the Analysis Tree later.} + */ + @Override + protected String getAnalysisTreeDataName() { + return "Group Distribution"; + } + + /** + * You can use the analysisTreeData array list to access the analysisTree data relevant for building your chart. + * @return the chart that will be used by the other class methods */ @Override Chart buildChart() { - return null; + + PieChart chart1 = new PieChartBuilder().build(); + + // todo sort keys for trees and in alphabetical order + for (String key : this.analysisTreeData.keySet()) { // für jeden Baum + @SuppressWarnings("unchecked") + HashMap nestedMap = (HashMap) this.analysisTreeData.get(key); + Set groupDistributionKeys = nestedMap.keySet(); + + for (String groupKey : groupDistributionKeys) { + ArrayList groupResult = (ArrayList) nestedMap.get(groupKey); + chart1.addSeries(groupKey, (Integer) groupResult.get(2)); + } + } + + // placeholder + /* + PieChart chart1 = new PieChartBuilder().build(); + chart1.addSeries("Banananana", 40); + chart1.addSeries("Apfel", 25); + chart1.addSeries("Gurke", 50); + + */ + + // data that we actually want to use + // System.out.println(this.analysisTreeData); + + return chart1; } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java deleted file mode 100644 index d46fa4ec..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureTypeDistribution.java +++ /dev/null @@ -1,24 +0,0 @@ -package de.featjar.feature.model.analysis.visualization; - -import de.featjar.feature.model.analysis.AnalysisTree; -import org.knowm.xchart.internal.chartpart.Chart; - -public class VisualizeFeatureTypeDistribution extends AVisualizeFeatureModelStats{ - public VisualizeFeatureTypeDistribution(AnalysisTree analysisTree) { - super(analysisTree); - } - - public VisualizeFeatureTypeDistribution(AnalysisTree analysisTree, String chartTitle) { - super(analysisTree, chartTitle); - } - - /** - * - * {@return the chart that will be used by the other class methods} - */ - @Override - Chart buildChart() { - return null; - } - -} diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index ee90b81e..20c25215 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -194,6 +194,7 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc HashMap computational_opDensity = Computations.of(model).map(ComputeOperatorDistribution::new).compute(); + //data.put("Operator Distribution", computational_opDensity); if (computational_opDensity.size() != 0) { data.put("Operator Distribution", computational_opDensity); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 34a5ff9b..56a04ec4 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -75,7 +75,8 @@ public Result compute(List dependencyList, Progress progress) { COUNTBOOLEAN.get(dependencyList))) .orElse(0); } - return Result.of( - (float) atomsSum / (float) featureModel.getConstraints().size()); + int numberOfConstraints = featureModel.getConstraints().size(); + float averageConstraint = (numberOfConstraints == 0)? 0 : (float) atomsSum / (float) numberOfConstraints; + return Result.of(averageConstraint); } } From 802cec7b77f0bac8cdcb1d610a4b865dbdf7b64b Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 14 Oct 2025 16:48:29 +0200 Subject: [PATCH 172/257] doc: added small todo reminder for ourselves --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index bd3a431f..663ba1c9 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -99,6 +99,7 @@ public void extractAnalysisTree() throws RuntimeException { assert attributeResult != null : "Could not retrieve data called " + key + " from AnalysisTree."; if (attributeResult instanceof Map) { + // todo instead of transferring the whole map, only transfer its get(2) value analysisTreeData.put(key, attributeResult); } else if (attributeResult instanceof ArrayList) { analysisTreeData.put(key, ((ArrayList) attributeResult).get(2)); From ab31c89e94eba3791014afc9418d93050caee2d7 Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 14 Oct 2025 18:27:59 +0200 Subject: [PATCH 173/257] feat: tried to refractoring and streamlining the extraction of the AnalysisTree in AVisualizeFeatureModelStats.java --- .../visualization/AVisualizeFeatureModelStats.java | 13 ++++++++++++- .../VisualizeFeatureGroupDistribution.java | 12 +++++++++--- 2 files changed, 21 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 663ba1c9..9b530c88 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -100,7 +100,18 @@ public void extractAnalysisTree() throws RuntimeException { if (attributeResult instanceof Map) { // todo instead of transferring the whole map, only transfer its get(2) value - analysisTreeData.put(key, attributeResult); + @SuppressWarnings("unchecked") + HashMap nestedMap = (HashMap) this.analysisTreeData.get(key); + Set groupKeys = nestedMap.keySet(); + // TODO sort keys for trees and in alphabetical order + //nicht getestet //Set sortedGroupKeys = FeatJAR.sortSetAlphabetically(groupKeys); + for (String groupKey : groupKeys) { + ArrayList groupResult = (ArrayList) nestedMap.get(groupKey); + String resultKey = key + " " + groupKey; + // TODO check if index 2 is always correct, or if there is a better way to access the value + analysisTreeData.put(resultKey, groupResult.get(2)); + + } } else if (attributeResult instanceof ArrayList) { analysisTreeData.put(key, ((ArrayList) attributeResult).get(2)); } else { diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java index 31df2272..771649a8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java @@ -12,7 +12,7 @@ public class VisualizeFeatureGroupDistribution extends AVisualizeFeatureModelStats{ public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree) { - super(analysisTree); + super(analysisTree, "Group Distribution"); } public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree, String chartTitle) { @@ -37,7 +37,13 @@ protected String getAnalysisTreeDataName() { PieChart chart1 = new PieChartBuilder().build(); - // todo sort keys for trees and in alphabetical order + for (String key : this.analysisTreeData.keySet()) { // für jede "gruppe pro Baum" + chart1.addSeries(key, (Integer) this.analysisTreeData.get(key)); + } + + + + /* for (String key : this.analysisTreeData.keySet()) { // für jeden Baum @SuppressWarnings("unchecked") HashMap nestedMap = (HashMap) this.analysisTreeData.get(key); @@ -47,7 +53,7 @@ protected String getAnalysisTreeDataName() { ArrayList groupResult = (ArrayList) nestedMap.get(groupKey); chart1.addSeries(groupKey, (Integer) groupResult.get(2)); } - } + }*/ // placeholder /* From 745517163be3f5b09a2157c1b72bf204e680799c Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Tue, 14 Oct 2025 18:56:42 +0200 Subject: [PATCH 174/257] doc: added small todo reminder --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 9b530c88..0ecad93f 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -20,7 +20,7 @@ public abstract class AVisualizeFeatureModelStats { final protected AnalysisTree analysisTree; - protected HashMap analysisTreeData = null; + protected HashMap analysisTreeData = null; // todo maybe make this a linked hash map if we sort trees alphabetically and want to remember the order private Chart chart; private String chartTitle = "Chart"; From 75950f0d0de24c2ea7dd390bc073fa8f892b7ad3 Mon Sep 17 00:00:00 2001 From: Valentin Date: Tue, 14 Oct 2025 19:09:06 +0200 Subject: [PATCH 175/257] fix: fixed typo --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 0ecad93f..466bd5f4 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -101,7 +101,8 @@ public void extractAnalysisTree() throws RuntimeException { if (attributeResult instanceof Map) { // todo instead of transferring the whole map, only transfer its get(2) value @SuppressWarnings("unchecked") - HashMap nestedMap = (HashMap) this.analysisTreeData.get(key); + // attributeResult.get() oder nur attributeResult? + HashMap nestedMap = (HashMap) attributeResult.get(); Set groupKeys = nestedMap.keySet(); // TODO sort keys for trees and in alphabetical order //nicht getestet //Set sortedGroupKeys = FeatJAR.sortSetAlphabetically(groupKeys); From 17a9a550b6fa9b74f64bfcb5c1bf2714d94d93c8 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 10:31:54 +0200 Subject: [PATCH 176/257] refactor: redone the data structure for the extracted Analysis Tree --- .../AVisualizeFeatureModelStats.java | 62 +++++++++++-------- .../model/analysis/visualization/Testtmp.java | 1 - .../VisualizeFeatureGroupDistribution.java | 16 +++-- 3 files changed, 48 insertions(+), 31 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 466bd5f4..8415f48d 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -20,7 +20,7 @@ public abstract class AVisualizeFeatureModelStats { final protected AnalysisTree analysisTree; - protected HashMap analysisTreeData = null; // todo maybe make this a linked hash map if we sort trees alphabetically and want to remember the order + protected LinkedHashMap> analysisTreeData; private Chart chart; private String chartTitle = "Chart"; @@ -31,8 +31,9 @@ public abstract class AVisualizeFeatureModelStats { public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { this.analysisTree = analysisTree; try { - this.extractAnalysisTree(); + this.analysisTreeData = extractAnalysisTree(); } catch (Exception e) { + FeatJAR.log().error(e); System.out.println(e); } this.chart = buildChart(); @@ -72,55 +73,66 @@ public void setHeight(Integer height) { /** * uses internal analysisTree and getAnalysisTreeDataName() to fetch the needed piece of data. */ - public void extractAnalysisTree() throws RuntimeException { + public LinkedHashMap> extractAnalysisTree() throws RuntimeException { // traverse tree to extract the general HashMap that more specific information is stored in. + // could probably be its own method AnalysisTreeVisitor visitor = new AnalysisTreeVisitor(); Result> result = Trees.traverse(analysisTree, visitor); HashMap receivedResult = result.get(); assert receivedResult != null: "Analysis Tree Visitor failed to produce a result."; @SuppressWarnings("unchecked") - HashMap analysisMap = (HashMap) receivedResult.get("Analysis"); + HashMap analysisMap = (HashMap) receivedResult.get("Analysis"); // we currently trust that this is always "Analysis" assert analysisMap != null: "Received no \"Analysis\" HashMap from AnalysisTree"; // build keys for each tree + // example: [Tree 1] Group Distribution, [Tree 2] Group Distribution, ... + // could probably be its own method String statName = this.getAnalysisTreeDataName(); - Set analysisMapKeys = analysisMap.keySet(); - ArrayList relevantKeys = new ArrayList<>(); - for (String key : analysisMapKeys) { + + List sortedAnalysisMapKeys = new ArrayList<>(analysisMap.keySet()); + Collections.sort(sortedAnalysisMapKeys); + ArrayList featureTreeDataKeys = new ArrayList<>(); + for (String key : sortedAnalysisMapKeys) { if (key.contains(statName)) { - relevantKeys.add(key); + featureTreeDataKeys.add(key); } } + // this works better with lists than arrays for some reason + LinkedHashMap> analysisTreeData = new LinkedHashMap<>(); + // for each tree - HashMap analysisTreeData = new HashMap<>(); - for (String key : relevantKeys) { + for (String key : featureTreeDataKeys) { + LinkedHashMap featureTreeData = new LinkedHashMap<>(); Object attributeResult = analysisMap.get(key); - assert attributeResult != null : "Could not retrieve data called " + key + " from AnalysisTree."; + assert attributeResult != null : "Could not retrieve data called \"" + key + "\" from AnalysisTree."; if (attributeResult instanceof Map) { - // todo instead of transferring the whole map, only transfer its get(2) value @SuppressWarnings("unchecked") - // attributeResult.get() oder nur attributeResult? - HashMap nestedMap = (HashMap) attributeResult.get(); - Set groupKeys = nestedMap.keySet(); - // TODO sort keys for trees and in alphabetical order - //nicht getestet //Set sortedGroupKeys = FeatJAR.sortSetAlphabetically(groupKeys); - for (String groupKey : groupKeys) { - ArrayList groupResult = (ArrayList) nestedMap.get(groupKey); - String resultKey = key + " " + groupKey; - // TODO check if index 2 is always correct, or if there is a better way to access the value - analysisTreeData.put(resultKey, groupResult.get(2)); + HashMap nestedMap = (HashMap) attributeResult; + + // sort keys alphabetically for consistency in the order they'll be displayed on the chart + List sortedNestedMapKeys = new ArrayList<>(nestedMap.keySet()); + Collections.sort(sortedNestedMapKeys); + for (String nestedMapKey : sortedNestedMapKeys) { + // the value relevant for us needs to be unpacked first + Object rawValue = nestedMap.get(nestedMapKey); + ArrayList castedValue = (ArrayList) rawValue; + Object value = castedValue.get(2); + + featureTreeData.put(nestedMapKey, value); } } else if (attributeResult instanceof ArrayList) { - analysisTreeData.put(key, ((ArrayList) attributeResult).get(2)); + ArrayList castedValue = (ArrayList) attributeResult; + Object value = castedValue.get(2); + featureTreeData.put(getAnalysisTreeDataName(), value); } else { throw new RuntimeException("Analysis Tree contained unknown data type in key " + key); } + analysisTreeData.put(key, featureTreeData); } - - this.analysisTreeData = analysisTreeData; + return analysisTreeData; } /** diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java index 3b8c5426..59dd1b90 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java @@ -84,7 +84,6 @@ public static FeatureModel generateMediumTree() { } public static void main(String[] args) { - System.out.println("Hello"); PrintStatistics printStatistics = new PrintStatistics(); LinkedHashMap map = printStatistics.collectStats( generateMediumTree(), diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java index 771649a8..9ff1a095 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java @@ -35,11 +35,19 @@ protected String getAnalysisTreeDataName() { @Override Chart buildChart() { - PieChart chart1 = new PieChartBuilder().build(); + ArrayList> charts = new ArrayList<>(); + for (String treeKey : this.analysisTreeData.keySet()) { + PieChart chart = new PieChartBuilder().build(); + HashMap treeData = analysisTreeData.get(treeKey); + for (String key: treeData.keySet()) { + chart.addSeries(key, (Integer) treeData.get(key)); + } + chart.setTitle(treeKey); - for (String key : this.analysisTreeData.keySet()) { // für jede "gruppe pro Baum" - chart1.addSeries(key, (Integer) this.analysisTreeData.get(key)); + charts.add(chart); } + + return charts.get(0); @@ -66,7 +74,5 @@ protected String getAnalysisTreeDataName() { // data that we actually want to use // System.out.println(this.analysisTreeData); - - return chart1; } } From 1cfa4a5125b3e94df84eab329c6f25fef930368c Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Wed, 15 Oct 2025 11:34:03 +0200 Subject: [PATCH 177/257] feat: format conversion generally working --- .../cli/ConfigurationFormatConversion.java | 104 ++++++++++++------ 1 file changed, 69 insertions(+), 35 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 6413d0cc..8b4cac07 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -29,6 +29,7 @@ import de.featjar.base.io.format.IFormat; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeatureModel; +import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; import de.featjar.feature.model.io.FeatureModelFormats; import de.featjar.formula.assignment.BooleanAssignmentList; import de.featjar.formula.io.BooleanAssignmentListFormats; @@ -36,6 +37,8 @@ import de.featjar.formula.io.binary.BooleanAssignmentListBinaryFormat; import de.featjar.formula.io.csv.BooleanAssignmentListCSVFormat; import de.featjar.formula.io.dimacs.BooleanAssignmentListDimacsFormat; +import de.featjar.formula.io.textual.BooleanAssignmentListSimpleTextFormat; +import de.featjar.formula.io.textual.BooleanAssignmentListTextFormat; import java.io.IOException; import java.nio.file.Files; @@ -59,6 +62,11 @@ // BooleanAssignmentValueMapFormat implements IFormat public class ConfigurationFormatConversion implements ICommand { + + public enum TypeTXT { + SIMPLETXT, + TXT, + } private static final List supportedInputFileExtensions = BooleanAssignmentListFormats.getInstance().getExtensions().stream() @@ -81,6 +89,9 @@ public class ConfigurationFormatConversion implements ICommand { public static final Option OVERWRITE = Option.newFlag("overwrite").setDescription("Overwrite output file."); + + public static final Option TypeTXT = + Option.newEnumOption("typetxt", TypeTXT.class).setDescription("Necessary if output is desired to be .list"); /** * @return all options registered for the calling class. @@ -147,34 +158,30 @@ public String toString() { @Override public int run(OptionList optionParser) { - /* - Possible IBooleanAssignmentListFormats: - BooleanAssignmentListBinaryFormat - BooleanAssignmentListCSVFormat - BooleanAssignmentListDimacsFormat - BooleanAssignmentListSimpleTextFormat - BooleanAssignmentListTextFormat - */ - - String outputFileExtension = - IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); - - - IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).orElseThrow()); - - Result load = IO.load(optionParser.getResult(INPUT_OPTION).orElseThrow(), BooleanAssignmentListFormats.getInstance()); - - Optional> outputFormats = BooleanAssignmentListFormats.getInstance().getExtensions().stream() - .filter(IFormat::supportsWrite) - .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) - .findFirst(); - try { - IO.save(load.get(), optionParser.getResult(OUTPUT_OPTION).orElseThrow(), outputFormats.get()); - } catch (Exception e) { - System.out.println(e); - } + +// String outputFileExtension; +// +// if (optionParser.getResult(TypeTXT).isPresent()) { +// outputFileExtension = optionParser.getResult(TypeTXT).get().toString(); +// } else { +// outputFileExtension = +// IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); +// } - saveFile(optionParser.getResult(OUTPUT_OPTION).orElseThrow(), outputFileExtension, optionParser.get(OVERWRITE)); + String outputFileExtension = IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get().toString()); + + if(outputFileExtension != "list" && optionParser.getResult(TypeTXT).isPresent()){ + FeatJAR.log().warning("Conflicting CLI options: " + outputFileExtension + " format in Path and TXT format due to --typetxt.\n Continuing to use TXT format..."); + } + + if(!checkIfInputOutputIsPresent(optionParser)) { + return 1; + }; + + + BooleanAssignmentList model = IO.load(optionParser.getResult(INPUT_OPTION).orElseThrow(), BooleanAssignmentListFormats.getInstance()).get(); + + saveFile(optionParser.getResult(OUTPUT_OPTION).orElseThrow(), model, outputFileExtension, optionParser.get(OVERWRITE)); // // @@ -402,22 +409,43 @@ private Result inputParser(OptionList optionParser) { * 4 if a file is already present at output path and no overwrite is specified * 5 on IOException */ - public int saveFile(Path outputPath, String outputFileExtension, boolean overWriteOutputFile) { - - - IO.getFileExtension(outputPath); - - Result load = IO.load(outputPath, BooleanAssignmentListFormats.getInstance()); + public int saveFile(Path outputPath, BooleanAssignmentList model, String outputFileExtension, boolean overWriteOutputFile) { + IFormat format; Optional> outputFormats = BooleanAssignmentListFormats.getInstance().getExtensions().stream() .filter(IFormat::supportsWrite) .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) .findFirst(); + + if (outputFormats.isEmpty() && !outputFileExtension.equals("TXT") && !outputFileExtension.equals("SIMPLETXT")) { + FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); + return 2; + } else if (outputFileExtension.equals("TXT")){ + outputPath = changeOutputExtension(outputPath, "txt"); + format = new BooleanAssignmentListTextFormat(); + } else if (outputFileExtension.equals("SIMPLETXT")) { + outputPath = changeOutputExtension(outputPath, "txt"); + format = new BooleanAssignmentListSimpleTextFormat(); + } else { + format = outputFormats.get(); + } try { - IO.save(load.get(), outputPath, outputFormats.get()); + if(Files.exists(outputPath)) { + if (overWriteOutputFile) { + FeatJAR.log().info("File already present at: \" + outputPath + \". Continuing to overwrite File."); + } else { + FeatJAR.log() + .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath + + ". To overwrite present file add --overwrite"); + return 4; + } + } + IO.save(model, outputPath, format); + } catch (Exception e) { - System.out.println(e); + FeatJAR.log().error(e); } + FeatJAR.log().message("Output model saved at: " + outputPath); // IFormat format; // @@ -453,6 +481,12 @@ public int saveFile(Path outputPath, String outputFileExtension, boolean overWri return 0; } + public Path changeOutputExtension(Path path, String extension) { + String s = path.toString().replace(IO.getFileExtension(path), extension.toLowerCase()); + return Paths.get(s); + } + + /** * * {@return brief description of this class} From 70d33bd8ccc485c5b24cd33cbd7d441a52f04b7e Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 11:53:59 +0200 Subject: [PATCH 178/257] refactor | feat: method for building pie charts transferred to abstract class to make the code work lighter on concrete implementations. Added some error handling. --- .../AVisualizeFeatureModelStats.java | 189 ++++++++++++------ .../VisualizeFeatureGroupDistribution.java | 59 +----- 2 files changed, 133 insertions(+), 115 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 8415f48d..1280bc53 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -5,10 +5,13 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; +import org.knowm.xchart.PieChart; +import org.knowm.xchart.PieChartBuilder; import org.knowm.xchart.SwingWrapper; import org.knowm.xchart.internal.chartpart.Chart; import java.util.*; +import java.util.stream.Collectors; /** * A class that builds a visualization using the XChart library. @@ -21,31 +24,24 @@ public abstract class AVisualizeFeatureModelStats { final protected AnalysisTree analysisTree; protected LinkedHashMap> analysisTreeData; - private Chart chart; - - private String chartTitle = "Chart"; - private Integer width; - private Integer height; + final protected ArrayList> charts; + private Integer width = 800; + private Integer height = 600; public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { this.analysisTree = analysisTree; try { this.analysisTreeData = extractAnalysisTree(); } catch (Exception e) { - FeatJAR.log().error(e); System.out.println(e); } - this.chart = buildChart(); - } - - public AVisualizeFeatureModelStats(AnalysisTree analysisTree, String chartTitle) { - this(analysisTree); - this.chartTitle = chartTitle; + this.charts = buildCharts(); + chartsAreEmpty(); } - public Chart getChart() { - return this.chart; + public ArrayList> getCharts() { + return this.charts; } public Integer getWidth() { @@ -64,67 +60,66 @@ public void setHeight(Integer height) { this.height = height; } + /** + * Checks if any charts are available and if not reminds the user of this fact with a warning message. + * @return True if charts exist, else false. + */ + protected boolean chartsAreEmpty() { + if (this.charts.isEmpty()) { + FeatJAR.log().warning(this.getClass().getName() + " did not build any charts!"); + return true; + } + return false; + } + /** * - * {@return String key used to fetch data from the Analysis Tree later.} + * {@return String key used to fetch data from the Analysis Tree.} */ protected abstract String getAnalysisTreeDataName(); /** - * uses internal analysisTree and getAnalysisTreeDataName() to fetch the needed piece of data. + * Uses internal analysisTree and getAnalysisTreeDataName() to fetch the needed piece of data from the analysis tree. + *

+ * {@return alphabetically sorted map with one key per tree. + * Each value is another HashMap with one entry per piece of data extracted from the Analysis Tree. These pieces of data are + * also alphabetically sorted.} */ public LinkedHashMap> extractAnalysisTree() throws RuntimeException { - // traverse tree to extract the general HashMap that more specific information is stored in. - // could probably be its own method - AnalysisTreeVisitor visitor = new AnalysisTreeVisitor(); - Result> result = Trees.traverse(analysisTree, visitor); - HashMap receivedResult = result.get(); - assert receivedResult != null: "Analysis Tree Visitor failed to produce a result."; - @SuppressWarnings("unchecked") - HashMap analysisMap = (HashMap) receivedResult.get("Analysis"); // we currently trust that this is always "Analysis" - assert analysisMap != null: "Received no \"Analysis\" HashMap from AnalysisTree"; + HashMap analysisMap = extractAnalysisMap(); - // build keys for each tree + // fetches keys for all trees for the data we want // example: [Tree 1] Group Distribution, [Tree 2] Group Distribution, ... - // could probably be its own method - String statName = this.getAnalysisTreeDataName(); - - List sortedAnalysisMapKeys = new ArrayList<>(analysisMap.keySet()); - Collections.sort(sortedAnalysisMapKeys); - ArrayList featureTreeDataKeys = new ArrayList<>(); - for (String key : sortedAnalysisMapKeys) { - if (key.contains(statName)) { - featureTreeDataKeys.add(key); - } - } + List featureTreeDataKeys = analysisMap.keySet().stream() + .filter(key -> key.contains(this.getAnalysisTreeDataName())) + .sorted() + .collect(Collectors.toList()); - // this works better with lists than arrays for some reason + // preparing return value LinkedHashMap> analysisTreeData = new LinkedHashMap<>(); - // for each tree + // for each tree: add a HashMap containing values per piece of information we need to extract for (String key : featureTreeDataKeys) { LinkedHashMap featureTreeData = new LinkedHashMap<>(); - Object attributeResult = analysisMap.get(key); - assert attributeResult != null : "Could not retrieve data called \"" + key + "\" from AnalysisTree."; + Object pieceOfInformation = analysisMap.get(key); + assert pieceOfInformation != null : "Could not retrieve data called \"" + key + "\" from AnalysisTree."; - if (attributeResult instanceof Map) { + if (pieceOfInformation instanceof Map) { @SuppressWarnings("unchecked") - HashMap nestedMap = (HashMap) attributeResult; - - // sort keys alphabetically for consistency in the order they'll be displayed on the chart - List sortedNestedMapKeys = new ArrayList<>(nestedMap.keySet()); - Collections.sort(sortedNestedMapKeys); - - for (String nestedMapKey : sortedNestedMapKeys) { - // the value relevant for us needs to be unpacked first - Object rawValue = nestedMap.get(nestedMapKey); - ArrayList castedValue = (ArrayList) rawValue; - Object value = castedValue.get(2); - - featureTreeData.put(nestedMapKey, value); - } - } else if (attributeResult instanceof ArrayList) { - ArrayList castedValue = (ArrayList) attributeResult; + HashMap nestedMap = (HashMap) pieceOfInformation; + + nestedMap.keySet().stream() + .sorted() + .forEach(nestedMapKey -> { + // the value relevant for us needs to be unpacked first + Object rawValue = nestedMap.get(nestedMapKey); + ArrayList castedValue = (ArrayList) rawValue; + Object value = castedValue.get(2); + featureTreeData.put(nestedMapKey, value); + }); + + } else if (pieceOfInformation instanceof ArrayList) { + ArrayList castedValue = (ArrayList) pieceOfInformation; Object value = castedValue.get(2); featureTreeData.put(getAnalysisTreeDataName(), value); } else { @@ -136,19 +131,89 @@ public LinkedHashMap> extractAnalysisTree( } /** - * You can use the analysisTreeData array list to access the analysisTree data relevant for building your chart. + * {@return the {@link AnalysisTree}'s general HashMap that more specific information is stored in.} + */ + private HashMap extractAnalysisMap() { + Result> result = Trees.traverse(analysisTree, new AnalysisTreeVisitor()); + HashMap receivedResult = result.get(); + assert receivedResult != null: "Analysis Tree Visitor failed to produce a result."; + + // we currently trust that this is always "Analysis" + @SuppressWarnings("unchecked") + HashMap analysisMap = (HashMap) receivedResult.get("Analysis"); + assert analysisMap != null: "Received no \"Analysis\" HashMap from AnalysisTree"; + + return analysisMap; + } + + /** + * Builds one chart per tree in the feature model. + * You can use the analysisTreeData to access the analysisTree data relevant for building your chart. + * See extractAnalysisTree() for details. * @return the chart that will be used by the other class methods */ - abstract Chart buildChart(); + abstract ArrayList> buildCharts(); + /** + * Premade builder for pie charts that you can use when implementing buildCharts(). + * @return one pie chart per tree in the feature model + */ + protected ArrayList> buildPieCharts() { + ArrayList> charts = new ArrayList<>(); + for (String treeKey : this.analysisTreeData.keySet()) { + PieChart chart = new PieChartBuilder() + .width(getWidth()) + .height(getHeight()) + .build(); + HashMap treeData = analysisTreeData.get(treeKey); + for (String key: treeData.keySet()) { + chart.addSeries(key, (Integer) treeData.get(key)); + } + chart.setTitle(treeKey); + + charts.add(chart); + } + return charts; + } /** * Creates a live preview pop-up window of the internally generated chart. + * Can take charts from its internal list, or external charts. + */ + public void displayChart (Chart chart) { + if (chartsAreEmpty()) {return;} + new SwingWrapper<>(chart).displayChart(); + } + + /** + * Creates a live preview pop-up window of the FIRST internally generated chart. + * This chart usually corresponds to the first feature tree in the feature model. */ public void displayChart() { - new SwingWrapper<>(this.chart).displayChart(); + if (chartsAreEmpty()) {return;} + this.displayChart(0); + } + + /** + * Creates a live preview pop-up window of an internally generated chart. + * Picks a chart from the internal list according to index. + */ + public void displayChart (Integer index) { + if (chartsAreEmpty()) {return;} + this.displayChart(this.charts.get(index)); + } + + /** + * Creates live preview pop-up windows of ALL internally generated charts. + */ + public void displayAllCharts() { + if (chartsAreEmpty()) {return;} + + for (Chart chart : this.charts) { + this.displayChart(chart); + } } - // TODO pdf export hinzufügen + // TODO pdf export } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java index 9ff1a095..fbbeb02a 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java @@ -1,31 +1,23 @@ package de.featjar.feature.model.analysis.visualization; import de.featjar.feature.model.analysis.AnalysisTree; -import org.knowm.xchart.PieChart; -import org.knowm.xchart.PieChartBuilder; -import org.knowm.xchart.QuickChart; import org.knowm.xchart.internal.chartpart.Chart; import java.util.ArrayList; -import java.util.HashMap; -import java.util.Set; public class VisualizeFeatureGroupDistribution extends AVisualizeFeatureModelStats{ public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree) { - super(analysisTree, "Group Distribution"); - } - - public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree, String chartTitle) { - super(analysisTree, chartTitle); + super(analysisTree); } /** * - * {@return String key used to fetch data from the Analysis Tree later.} + * {@return String key used to fetch data from the Analysis Tree.} */ @Override protected String getAnalysisTreeDataName() { - return "Group Distribution"; + return "Number of Top Features"; + //return "Group Distribution"; } /** @@ -33,46 +25,7 @@ protected String getAnalysisTreeDataName() { * @return the chart that will be used by the other class methods */ @Override - Chart buildChart() { - - ArrayList> charts = new ArrayList<>(); - for (String treeKey : this.analysisTreeData.keySet()) { - PieChart chart = new PieChartBuilder().build(); - HashMap treeData = analysisTreeData.get(treeKey); - for (String key: treeData.keySet()) { - chart.addSeries(key, (Integer) treeData.get(key)); - } - chart.setTitle(treeKey); - - charts.add(chart); - } - - return charts.get(0); - - - - /* - for (String key : this.analysisTreeData.keySet()) { // für jeden Baum - @SuppressWarnings("unchecked") - HashMap nestedMap = (HashMap) this.analysisTreeData.get(key); - Set groupDistributionKeys = nestedMap.keySet(); - - for (String groupKey : groupDistributionKeys) { - ArrayList groupResult = (ArrayList) nestedMap.get(groupKey); - chart1.addSeries(groupKey, (Integer) groupResult.get(2)); - } - }*/ - - // placeholder - /* - PieChart chart1 = new PieChartBuilder().build(); - chart1.addSeries("Banananana", 40); - chart1.addSeries("Apfel", 25); - chart1.addSeries("Gurke", 50); - - */ - - // data that we actually want to use - // System.out.println(this.analysisTreeData); + ArrayList> buildCharts() { + return buildPieCharts(); } } From 137bd7dd1cd209380a18fe6c5119177d190eee13 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 12:09:47 +0200 Subject: [PATCH 179/257] doc: general doc updates --- .../AVisualizeFeatureModelStats.java | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 1280bc53..f208ec92 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -147,16 +147,15 @@ private HashMap extractAnalysisMap() { } /** - * Builds one chart per tree in the feature model. - * You can use the analysisTreeData to access the analysisTree data relevant for building your chart. - * See extractAnalysisTree() for details. - * @return the chart that will be used by the other class methods + * Use analysisTreeData to access the data relevant for building your chart. + * There are also premade builders that you may adopt. + * @return list containing one chart per tree in the feature model */ abstract ArrayList> buildCharts(); /** * Premade builder for pie charts that you can use when implementing buildCharts(). - * @return one pie chart per tree in the feature model + * @return list containing one chart per tree in the feature model */ protected ArrayList> buildPieCharts() { ArrayList> charts = new ArrayList<>(); @@ -177,8 +176,7 @@ private HashMap extractAnalysisMap() { } /** - * Creates a live preview pop-up window of the internally generated chart. - * Can take charts from its internal list, or external charts. + * Creates a live preview pop-up window of a chart. */ public void displayChart (Chart chart) { if (chartsAreEmpty()) {return;} @@ -195,8 +193,7 @@ public void displayChart() { } /** - * Creates a live preview pop-up window of an internally generated chart. - * Picks a chart from the internal list according to index. + * Creates a live preview pop-up window of an internally generated chart, fetched by index. */ public void displayChart (Integer index) { if (chartsAreEmpty()) {return;} From 94658eba70df9f51c4da77f2e2be7bd8b5883742 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 12:26:44 +0200 Subject: [PATCH 180/257] style: removed dummy exception handling --- .../visualization/AVisualizeFeatureModelStats.java | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index f208ec92..1738f2ea 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -31,11 +31,7 @@ public abstract class AVisualizeFeatureModelStats { public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { this.analysisTree = analysisTree; - try { - this.analysisTreeData = extractAnalysisTree(); - } catch (Exception e) { - System.out.println(e); - } + this.analysisTreeData = extractAnalysisTree(); this.charts = buildCharts(); chartsAreEmpty(); } @@ -73,7 +69,6 @@ protected boolean chartsAreEmpty() { } /** - * * {@return String key used to fetch data from the Analysis Tree.} */ protected abstract String getAnalysisTreeDataName(); From bbcebb016007e40a13149d43b106cb623c9cbc20 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 12:27:19 +0200 Subject: [PATCH 181/257] style: renamed class to be more succint --- ...pDistribution.java => VisualizeGroupDistribution.java} | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) rename src/main/java/de/featjar/feature/model/analysis/visualization/{VisualizeFeatureGroupDistribution.java => VisualizeGroupDistribution.java} (73%) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java similarity index 73% rename from src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java rename to src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java index fbbeb02a..a0a5a051 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeFeatureGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java @@ -5,19 +5,17 @@ import java.util.ArrayList; -public class VisualizeFeatureGroupDistribution extends AVisualizeFeatureModelStats{ - public VisualizeFeatureGroupDistribution(AnalysisTree analysisTree) { +public class VisualizeGroupDistribution extends AVisualizeFeatureModelStats{ + public VisualizeGroupDistribution(AnalysisTree analysisTree) { super(analysisTree); } /** - * * {@return String key used to fetch data from the Analysis Tree.} */ @Override protected String getAnalysisTreeDataName() { - return "Number of Top Features"; - //return "Group Distribution"; + return "Group Distribution"; } /** From d84cb2a5f5271baf93241457d04d4b86eb7d429e Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 12:42:27 +0200 Subject: [PATCH 182/257] refactor: improved logging for empty charts --- .../AVisualizeFeatureModelStats.java | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 1738f2ea..e1795f11 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -58,16 +58,24 @@ public void setHeight(Integer height) { /** * Checks if any charts are available and if not reminds the user of this fact with a warning message. + * @param extraMessage Will be logged before the main message, for example to specify what task cannot be completed * @return True if charts exist, else false. */ - protected boolean chartsAreEmpty() { + protected boolean chartsAreEmpty(String extraMessage) { + if (this.charts.isEmpty()) { - FeatJAR.log().warning(this.getClass().getName() + " did not build any charts!"); + FeatJAR.log().warning(extraMessage + this.getClass().getName() + " did not build any charts!"); return true; } return false; } + private boolean chartsAreEmpty() {return chartsAreEmpty("");} + + private boolean chartsAreEmptyDisplay() { + return chartsAreEmpty("Cannot display chart: "); + } + /** * {@return String key used to fetch data from the Analysis Tree.} */ @@ -159,12 +167,11 @@ private HashMap extractAnalysisMap() { .width(getWidth()) .height(getHeight()) .build(); - HashMap treeData = analysisTreeData.get(treeKey); - for (String key: treeData.keySet()) { - chart.addSeries(key, (Integer) treeData.get(key)); - } chart.setTitle(treeKey); + HashMap treeData = analysisTreeData.get(treeKey); + treeData.forEach((key, value) -> chart.addSeries(key, (Integer) value)); + charts.add(chart); } return charts; @@ -174,7 +181,7 @@ private HashMap extractAnalysisMap() { * Creates a live preview pop-up window of a chart. */ public void displayChart (Chart chart) { - if (chartsAreEmpty()) {return;} + if (chartsAreEmptyDisplay()) {return;} new SwingWrapper<>(chart).displayChart(); } @@ -183,7 +190,7 @@ public void displayChart (Chart chart) { * This chart usually corresponds to the first feature tree in the feature model. */ public void displayChart() { - if (chartsAreEmpty()) {return;} + if (chartsAreEmptyDisplay()) {return;} this.displayChart(0); } @@ -191,7 +198,7 @@ public void displayChart() { * Creates a live preview pop-up window of an internally generated chart, fetched by index. */ public void displayChart (Integer index) { - if (chartsAreEmpty()) {return;} + if (chartsAreEmptyDisplay()) {return;} this.displayChart(this.charts.get(index)); } @@ -199,7 +206,7 @@ public void displayChart (Integer index) { * Creates live preview pop-up windows of ALL internally generated charts. */ public void displayAllCharts() { - if (chartsAreEmpty()) {return;} + if (chartsAreEmptyDisplay()) {return;} for (Chart chart : this.charts) { this.displayChart(chart); From 6210651023ba9d074b2981b91c1950885be97842 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 13:04:04 +0200 Subject: [PATCH 183/257] feat: Constraint Operator Distributions charts can now also be displayed --- .../model/analysis/visualization/Testtmp.java | 23 +- ...sualizeConstraintOperatorDistribution.java | 31 + .../model/analysis/visualization/model.xml | 19970 ++++++++++++++++ 3 files changed, 20022 insertions(+), 2 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/model.xml diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java index 59dd1b90..83ba71ef 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java @@ -1,13 +1,20 @@ package de.featjar.feature.model.analysis.visualization; +import de.featjar.base.data.Result; import de.featjar.base.data.identifier.Identifiers; +import de.featjar.base.io.IO; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeature; +import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.cli.PrintStatistics; +import de.featjar.feature.model.io.FeatureModelFormats; import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; +import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.LinkedHashMap; public class Testtmp { @@ -89,11 +96,23 @@ public static void main(String[] args) { generateMediumTree(), PrintStatistics.AnalysesScope.ALL ); - AnalysisTree analysisTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + AnalysisTree mediumAnalysisTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); - VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(analysisTree); + Path path = Paths.get("src/main/java/de/featjar/feature/model/analysis/visualization/model.xml"); + Result load = IO.load(path, new XMLFeatureModelFormat()); + FeatureModel model = (FeatureModel) load.orElseThrow(); + map = printStatistics.collectStats( + model, + PrintStatistics.AnalysesScope.ALL + ); + AnalysisTree bigAnalysisTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + + + VisualizeGroupDistribution viz = new VisualizeGroupDistribution(bigAnalysisTree); + //VisualizeConstraintOperatorDistribution viz = new VisualizeConstraintOperatorDistribution(bigAnalysisTree); //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(createDefaultTree()); //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(generateEmptyTree()); + viz.displayChart(); } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java new file mode 100644 index 00000000..7b595fd3 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java @@ -0,0 +1,31 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.internal.chartpart.Chart; + +import java.util.ArrayList; + +public class VisualizeConstraintOperatorDistribution extends AVisualizeFeatureModelStats{ + public VisualizeConstraintOperatorDistribution(AnalysisTree analysisTree) { + super(analysisTree); + } + + /** + * {@return String key used to fetch data from the Analysis Tree.} + */ + @Override + protected String getAnalysisTreeDataName() { + return "Operator Distribution"; + } + + /** + * Use analysisTreeData to access the data relevant for building your chart. + * There are also premade builders that you may adopt. + * + * @return list containing one chart per tree in the feature model + */ + @Override + ArrayList> buildCharts() { + return buildPieCharts(); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/model.xml b/src/main/java/de/featjar/feature/model/analysis/visualization/model.xml new file mode 100644 index 00000000..e645e9ad --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/model.xml @@ -0,0 +1,19970 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + F_6B23DD86CE4C + F_287D032B6EC5 + + + + + + F_1289140DEC8F + + + F_477DA48AC4E3 + + + + + + F_71ED362684B0 + + F_E0E6FF46990C + + + + + + F_71ED362684B0 + + F_2994A4D5A93B + + + + + + + F_E600CF21575B + F_C8A22DCFE3DA + + + F_2994A4D5A93B + + + + + + F_71ED362684B0 + + F_E600CF21575B + + + + + + + F_2994A4D5A93B + F_C8A22DCFE3DA + + + F_E600CF21575B + + + + + + F_71ED362684B0 + + F_C8A22DCFE3DA + + + + + + + F_2994A4D5A93B + F_E600CF21575B + + + F_C8A22DCFE3DA + + + + + + F_71ED362684B0 + + F_2A4A3F0CD628 + + + + + + F_71ED362684B0 + + F_872323098893 + + + + + + F_71ED362684B0 + + F_CF72A5DEF625 + + + + + + F_71ED362684B0 + + F_16539673D4C1 + + + + + + F_71ED362684B0 + + F_EF35B5A8ED26 + + + + + + F_71ED362684B0 + + F_183CA5A34D89 + + + + + + F_71ED362684B0 + + F_DF4FB3AEEFD2 + + + + + + + F_13E1D684FB37 + + F_BA73D0B4000C + + F_298AB7378BD9 + + F_E0EDAC1B0766 + + F_C45FAA9EFC52 + + F_53C4ED86721B + F_4658CED956BD + + + + + + + F_4C29DE5FB575 + + + + + + + + F_13E1D684FB37 + + F_BA73D0B4000C + + F_298AB7378BD9 + + F_E0EDAC1B0766 + + F_C45FAA9EFC52 + + F_53C4ED86721B + F_4658CED956BD + + + + + + + + F_4C29DE5FB575 + + + + + + F_BA73D0B4000C + F_E0BE25B9AD7D + + + + + + + F_BA73D0B4000C + + F_E0BE25B9AD7D + + + + + + F_298AB7378BD9 + F_06F9B017087A + + + + + + + F_298AB7378BD9 + + F_06F9B017087A + + + + + + F_E0EDAC1B0766 + F_AA4E8BAE18CA + + + + + + + F_E0EDAC1B0766 + + F_AA4E8BAE18CA + + + + + + F_4C29DE5FB575 + F_ED13EA06905B + + + + + + + F_4C29DE5FB575 + + F_ED13EA06905B + + + + + + F_4658CED956BD + F_CDC96E658D49 + + + + + + + F_4658CED956BD + + F_CDC96E658D49 + + + + + + F_C45FAA9EFC52 + F_D7565D42AC69 + + + + + + + F_C45FAA9EFC52 + + F_D7565D42AC69 + + + + + + F_53C4ED86721B + F_25B3E08AF0BF + + + + + + + F_53C4ED86721B + + F_25B3E08AF0BF + + + + + + F_941E0D178915 + F_99F5E615EC6C + + + + + + + F_941E0D178915 + + F_99F5E615EC6C + + + + + + F_F423DFBF8E73 + F_A7693637D091 + + + + + + + F_F423DFBF8E73 + + F_A7693637D091 + + + + + + F_4C33E5FDD98F + F_14AE801EFA46 + + + + + F_9B36F5B4B4A7 + F_92339DEA751D + + + + + F_6B5EF2E988B9 + F_1A0843F608A4 + + + + + F_1372FB623FE7 + F_972D7A9AC209 + + + + + F_D940FC181A7D + F_5A34032B4F67 + + + + + F_674B75035505 + F_5A34032B4F67 + + + + + F_64B23714BD05 + F_A3C1C48F3BAB + + + + + F_3C31715289F5 + F_A3C1C48F3BAB + + + + + F_3C31715289F5 + F_A3C1C48F3BAB + + + + + F_1FFA7809FE74 + + F_A0D82D3B41E7 + + + + + + F_1FFA7809FE74 + + F_1FAADCAC2E0C + + + + + + F_1FFA7809FE74 + + F_6597E2573D04 + + + + + + F_1FFA7809FE74 + + F_C3C6AE3DD993 + + + + + + F_1FFA7809FE74 + + F_A60B2A578AAD + + + + + + F_1FFA7809FE74 + + F_51AA2E5A6138 + + + + + + F_1FFA7809FE74 + + F_5216C3044DC9 + + + + + + F_1FFA7809FE74 + + F_C6DE1CC68AE2 + + + + + + F_1FFA7809FE74 + + F_F41047C32362 + + + + + + F_1FFA7809FE74 + + F_EE0446663B13 + + + + + + F_1FFA7809FE74 + + F_AC621D8DF312 + + + + + + F_1FFA7809FE74 + F_6349B771F41B + + + + + F_1FFA7809FE74 + + F_0C5D527774F3 + + + + + + F_1FFA7809FE74 + + F_3C31715289F5 + + + + + + F_1FFA7809FE74 + + F_A3C1C48F3BAB + + + + + + F_1FFA7809FE74 + + F_64B23714BD05 + + + + + + F_1FFA7809FE74 + F_29003B841DAD + + + + + + F_F8F2D28CFC79 + + + F_0FA973F9758F + + + + F_C8431FCB3B9F + + + F_05693D1B6958 + + + + + + + + + F_589E26BC870C + F_D2A298A77B1F + + + + + F_589E26BC870C + F_83FB3183339D + + + + + F_589E26BC870C + F_708712580461 + + + + + F_589E26BC870C + F_FB2465BABE54 + + + + + F_D2A298A77B1F + F_27CEAB8954AB + + + + + F_D2A298A77B1F + F_3D5A320D005E + + + + + F_D2A298A77B1F + F_EC6061387C1D + + + + + F_D2A298A77B1F + F_C38895D05BC8 + + + + + F_708712580461 + F_C2AB9E001BC0 + + + + + F_708712580461 + F_55122A29D50A + + + + + F_708712580461 + F_F1A64294DB2E + + + + + F_708712580461 + F_4E811328D007 + + + + + F_708712580461 + F_1EB6D3D911B8 + + + + + F_708712580461 + F_770DAC02854D + + + + + F_708712580461 + F_1FE44FFAD111 + + + + + F_708712580461 + F_2D928E3C12DF + + + + + F_708712580461 + F_C2A3F3CDCDBD + + + + + F_708712580461 + F_C48024B5A4F5 + + + + + F_708712580461 + F_330EA68C3A89 + + + + + F_708712580461 + F_CD59D9EE88EE + + + + + F_83FB3183339D + F_6A24FF0C06BE + + + + + F_83FB3183339D + F_F2855219479F + + + + + F_83FB3183339D + F_F4A8195A09F5 + + + + + F_83FB3183339D + F_64A3B14CAC81 + + + + + F_83FB3183339D + F_EF64D704554B + + + + + F_83FB3183339D + F_BA1C3372752E + + + + + F_83FB3183339D + F_9E4A4F97E323 + + + + + F_83FB3183339D + F_14CCBE94F2A7 + + + + + F_83FB3183339D + F_7552F0FA370A + + + + + F_83FB3183339D + F_1EDD6A0F7489 + + + + + F_83FB3183339D + F_9E01B082DA87 + + + + + F_83FB3183339D + F_C34D46621AA4 + + + + + F_FB2465BABE54 + F_66279FD1527A + + + + + F_FB2465BABE54 + F_968BAA64621A + + + + + F_FB2465BABE54 + F_AB197773C8D6 + + + + + F_C38895D05BC8 + F_BD0B4FA69621 + + + + + F_C38895D05BC8 + F_A5BBFA482946 + + + + + F_7552F0FA370A + F_D0B25C31B002 + + + + + F_7552F0FA370A + F_716B6C222717 + + + + + F_1EB6D3D911B8 + F_0C1DCB8D2F7C + + + + + F_1EB6D3D911B8 + F_1EC8B43215D6 + + + + + F_4E811328D007 + F_B4C5C5497F03 + + + + + F_4E811328D007 + F_C2A3F3CDCDBD + + + + + F_EE9476FFE732 + F_466576092406 + + + + + + + F_EE9476FFE732 + + F_466576092406 + + + + + + F_EE9476FFE732 + F_ED39CF9B120E + + + + + + + F_EE9476FFE732 + + F_ED39CF9B120E + + + + + + F_EE9476FFE732 + F_8E0692694689 + + + + + + + F_EE9476FFE732 + + F_8E0692694689 + + + + + + F_EE9476FFE732 + F_7C42BF391C11 + + + + + + + F_EE9476FFE732 + + F_300290481B1A + + + + + + + + F_D8A356C2678B + + F_D37C91A69D94 + + + + + + + + F_D8A356C2678B + + F_90CCCD098AA6 + + + + + + + + F_EE9476FFE732 + + F_D301A881B9D5 + + + + + + + + F_EE9476FFE732 + + F_491B5B403498 + + + + + + F_EE9476FFE732 + F_AD17FA95298D + + + + + + + F_EE9476FFE732 + + F_AD17FA95298D + + + + + + + + F_EE9476FFE732 + + F_861FAC4B2BBA + + + + + + + + F_EE9476FFE732 + + F_77DFAC79E119 + + + + + + + + F_EE9476FFE732 + + F_06FB1D35C431 + + + + + + + + F_49977D5B3C86 + + F_68A29EC10620 + + + + + + + + F_49977D5B3C86 + + F_DE57DC2BA683 + + + + + + + + F_49977D5B3C86 + + F_A117CF33BF99 + + + + + + + + F_49977D5B3C86 + + F_B9DE8DC6BBE8 + + + + + + + + F_49977D5B3C86 + + F_976B6B409804 + + + + + + + + F_49977D5B3C86 + + F_5EEF02682907 + + + + + + + + F_D8A356C2678B + + F_268B746ACEBA + + + + + + + + F_49977D5B3C86 + + F_88EA87962355 + + + + + + + + F_268B746ACEBA + + F_B58BB34CEAC5 + + + + + + F_268B746ACEBA + F_B58BB34CEAC5 + + + + + + F_EE9476FFE732 + F_49977D5B3C86 + + F_78444AD28000 + + + + + + + + F_EE9476FFE732 + + + F_49977D5B3C86 + + + F_78444AD28000 + + + + + + + F_EE9476FFE732 + + F_49977D5B3C86 + + F_95F7EBB7C597 + + F_DCE2F40B953E + F_4DA8596C3DB7 + + + + + F_EA045B5E8E7E + + + + + + + + F_EE9476FFE732 + + + + F_49977D5B3C86 + + + + F_95F7EBB7C597 + + + + F_DCE2F40B953E + + + F_4DA8596C3DB7 + + + + + + F_EA045B5E8E7E + + + + + + + + F_88EA87962355 + + F_356020ACABF5 + + + + + + F_88EA87962355 + F_356020ACABF5 + + + + + F_D37C91A69D94 + F_284D321910CE + + + + + + + F_D37C91A69D94 + + F_284D321910CE + + + + + + F_300290481B1A + F_1E9EEBAE0336 + + + + + + + F_300290481B1A + + F_1E9EEBAE0336 + + + + + + + + F_EE9476FFE732 + + F_26BB281A7B99 + + + + + + F_B58BB34CEAC5 + F_26BB281A7B99 + + + + + + + F_D301A881B9D5 + + F_AB93BAB9255A + + + + + + F_D301A881B9D5 + F_AB93BAB9255A + + + + + F_616623013F0E + F_37C3684F1C2A + + + + + + + F_616623013F0E + + F_37C3684F1C2A + + + + + + F_2B5CE92D540D + F_857BAA3AA7D1 + + + + + + + F_2B5CE92D540D + + F_857BAA3AA7D1 + + + + + + + F_5D2A38F3A36B + F_4A2D34DA0CA7 + + F_C984FD12989B + + + + + F_69996786985B + F_9F55FDBB2113 + + + + + + F_69996786985B + F_63729C518C0D + + F_362599757447 + + + + + F_48058A639832 + F_D1206C1976D7 + + + + + F_97FCA4DD2A93 + F_D46CCD87F719 + + + + + F_4D6152D7EFBF + F_37DEA03E7F1B + + + + + + F_4D6152D7EFBF + F_74DF894A309E + + F_FE46533917E7 + + + + + + F_0222D801A29F + + F_E06516521A78 + + F_54A766DB8678 + F_9559B9C2E682 + + + + F_EBB702D4168C + + + + + + F_81D18F0C63B0 + F_C984FD12989B + + F_3368E7E1EB69 + + + + + + F_4746737838ED + + F_4D6152D7EFBF + + F_74DF894A309E + F_2602C0552F1D + + + + F_202CA3F2FF6C + + + + + + F_5185EC6392FC + F_97FCA4DD2A93 + + F_CE2198A5795D + + + + + I_2575251467__F_E0CF8DBD6267 + F_0E265B10E6B0 + + + + + + I_7760305__F_0C1E8C5BE5C8 + + I_7760305__F_D82C159BB788 + F_0E265B10E6B0 + + + F_3DF122257B11 + + + + + I_2575251467__F_21083B75D869 + F_5BA578210CDE + + + + + + I_7760305__F_91DBBC47D7E3 + + I_7760305__F_D82C159BB788 + F_5BA578210CDE + + + F_0BED5E916352 + + + + + + I_2575251467__F_E4BC2D791F18 + + I_2575251467__F_56A88C783E40 + + I_2575251467__F_0F32255843CF + I_2575251467__F_421A0C507788 + + + + F_55D95DA906FE + + + + + + I_7760305__F_3A42D079DD81 + + I_7760305__F_11CEF3B43F11 + + I_7760305__F_7478CDE452E5 + + I_7760305__F_888CF84F1121 + + I_7760305__F_D82C159BB788 + F_55D95DA906FE + + + + + + F_C6D6D916FF14 + + + + + F_E19742FF52C1 + + F_735542C99DF0 + + + + + + F_E19742FF52C1 + + F_0609E78A5599 + + + + + + F_E19742FF52C1 + + F_CE856AA0437F + + + + + + F_E19742FF52C1 + + F_1289140DEC8F + + + + + + F_E19742FF52C1 + + F_477DA48AC4E3 + + + + + + F_E19742FF52C1 + + F_DA2BC2641BC3 + + + + + + F_E19742FF52C1 + F_71ED362684B0 + + + + + F_E19742FF52C1 + + F_A36FDFDBA2C2 + + + + + + F_100B9F633962 + + F_735542C99DF0 + + + + + + F_100B9F633962 + F_0609E78A5599 + + + + + F_100B9F633962 + + F_CE856AA0437F + + + + + + F_100B9F633962 + + F_A36FDFDBA2C2 + + + + + + F_862FD9AB518D + F_CE856AA0437F + + + + + F_862FD9AB518D + + F_735542C99DF0 + + + + + + F_862FD9AB518D + + F_0609E78A5599 + + + + + + F_862FD9AB518D + F_A36FDFDBA2C2 + + + + + F_E0E6FF46990C + F_33A21731000A + + + + + + + F_E0E6FF46990C + + F_33A21731000A + + + + + + F_2994A4D5A93B + F_11D862C3DB48 + + + + + + + F_2994A4D5A93B + + F_11D862C3DB48 + + + + + + F_E600CF21575B + F_CF48F3BCFB26 + + + + + + + F_E600CF21575B + + F_CF48F3BCFB26 + + + + + + F_C8A22DCFE3DA + F_2D921A4097DB + + + + + + + F_C8A22DCFE3DA + + F_2D921A4097DB + + + + + + F_2A4A3F0CD628 + F_41FDD62CC58C + + + + + + + F_2A4A3F0CD628 + + F_41FDD62CC58C + + + + + + F_872323098893 + F_5E11BF1FAECA + + + + + + + F_872323098893 + + F_5E11BF1FAECA + + + + + + F_CF72A5DEF625 + F_ED76D00FABD9 + + + + + + + F_CF72A5DEF625 + + F_ED76D00FABD9 + + + + + + F_16539673D4C1 + F_248886F97E8C + + + + + + + F_16539673D4C1 + + F_248886F97E8C + + + + + + F_EF35B5A8ED26 + F_5C35E07FCE2A + + + + + + + F_EF35B5A8ED26 + + F_5C35E07FCE2A + + + + + + F_183CA5A34D89 + F_6FEC7C59E6E7 + + + + + + + F_183CA5A34D89 + + F_6FEC7C59E6E7 + + + + + + + + + F_2B1D0DCD7979 + F_988C739F5296 + + + F_F423DFBF8E73 + + + + + + F_2B1D0DCD7979 + F_1466A6274D0F + + + + + + + F_2B1D0DCD7979 + + F_1466A6274D0F + + + + + + F_988C739F5296 + F_8199C75D1AD6 + + + + + + + F_988C739F5296 + + F_8199C75D1AD6 + + + + + + + F_1466A6274D0F + F_8199C75D1AD6 + + F_F3B7A4A31C71 + + + + + + + + F_1466A6274D0F + F_8199C75D1AD6 + + + F_F3B7A4A31C71 + + + + + + F_ABC265937F28 + F_98BA37D95350 + + + + + + + F_ABC265937F28 + + F_98BA37D95350 + + + + + + F_6349B771F41B + + F_00A19D40C68A + + + + + + F_6349B771F41B + + F_6151868A8737 + + + + + + F_6349B771F41B + + F_33782653184E + + + + + + F_6349B771F41B + + F_470317AAA79F + + + + + + F_00A19D40C68A + F_A3C1C48F3BAB + + + + + F_6151868A8737 + F_A3C1C48F3BAB + + + + + F_33782653184E + F_A3C1C48F3BAB + + + + + F_470317AAA79F + F_A3C1C48F3BAB + + + + + F_470317AAA79F + F_05693D1B6958 + + + + + + F_470317AAA79F + + + F_05693D1B6958 + + + + + + F_0FA973F9758F + F_F8F2D28CFC79 + + + + + F_C8431FCB3B9F + F_F8F2D28CFC79 + + + + + F_05693D1B6958 + F_F8F2D28CFC79 + + + + + F_B37CDA55BC53 + F_9430B12CD35F + + + + + F_36D5CD424069 + F_7BD341A32374 + + + + + F_DFEA67530CDB + F_4497A940C69B + + + + + F_DFEA67530CDB + F_91708477EA52 + + + + + F_05031F727B3F + F_98FBBD56D38F + + + + + + + F_05031F727B3F + + F_98FBBD56D38F + + + + + + F_CCED0D63B88F + F_9767B30CF1AF + + + + + + + F_CCED0D63B88F + + F_9767B30CF1AF + + + + + + F_2E7C625562B3 + F_3498516B3125 + + + + + F_4501797FDDD9 + F_44B06779E249 + + + + + F_A57855CB935C + F_96A9F2329532 + + + + + F_9464057C3183 + F_88B37BD015C4 + + + + + F_67DD40FAB428 + F_7711077E1354 + + + + + + F_74DF894A309E + F_69996786985B + + F_5B84EC723F39 + + + + + + F_125B566D5B71 + F_F785E9DB694C + + F_6952DFFB4E8C + + + + + + F_B709EC4C65DD + + F_F785E9DB694C + + + + + F_B709EC4C65DD + + F_F785E9DB694C + + + + + + + I_2979276807__F_9D6466EC9799 + I_570790106__F_803DE74C7964 + + F_B709EC4C65DD + + + + + + + I_2979276807__F_9D6466EC9799 + + + I_570790106__F_803DE74C7964 + + + + F_B709EC4C65DD + + + + + + F_E5DBEFFBF088 + F_E730F94A125F + + + + + F_0B5B64C7427E + F_9E903018A32E + + + + + F_B957D8524CF6 + F_8C5668B4FA26 + + + + + F_C81A340D6D50 + F_D526323E405D + + + + + F_76ADEAAD15A4 + F_DE155AD838AC + + + + + F_60E438EF127C + F_0300B0315E8F + + + + + F_053B2AA502D2 + F_DB3566ACE11A + + + + + F_70B49C1BB3C4 + F_432AB2FFF2A0 + + + + + F_82CE104B0ACC + F_21AC50209874 + + + + + F_19230C964CC8 + F_E79D3C8F6D6D + + + + + + F_125B566D5B71 + + F_55D95DA906FE + + F_C6D6D916FF14 + + F_90C6322BD63E + F_88B37BD015C4 + + + + + F_318F7F8EE99F + + + + + F_2602C0552F1D + + F_F315A57C3909 + F_A0D32788EBDC + + + + + + + F_69996786985B + + F_63729C518C0D + + F_19E9A8484E4D + F_0222D801A29F + + + + F_4A2D34DA0CA7 + + + + + F_7583595C7A47 + F_2567C2BB6C1F + + + + + F_CED8B94985E3 + F_7AA21F58181C + + + + + F_9C1EA50D1FED + F_BB6313B31088 + + + + + F_6738C75E8134 + F_7D5CCEFC1D1C + + + + + F_0B5B64C7427E + F_3A68237C7103 + + + + + F_9F9D288496E0 + F_F2983AAF88AA + + + + + F_8DADA6063D72 + F_D09133D9FBCE + + + + + F_E0AD228D0940 + F_E27528B21C7D + + + + + F_76ADEAAD15A4 + F_488ED4170E0B + + + + + F_DE6D85E0DCE0 + F_43705EA90AEA + + + + + F_7E561AFED8C9 + F_F2B36D30C2B2 + + + + + F_F695950B1533 + F_B11B7C43DA3A + + + + + F_F62946A5DFC7 + F_3601226BB731 + + + + + F_40A829148170 + F_6DC838904F56 + + + + + F_D8CC0E8A0992 + F_45F1FC08BA81 + + + + + F_847425E031D4 + F_C0E3CC7762E9 + + + + + F_D8331ACD2114 + F_83ED2CFE7E9D + + + + + F_8B8322BC200E + F_3787B7213205 + + + + + F_6B5D96189428 + F_27D82BCA436E + + + + + F_E3139CC2E0FA + F_F0875AE17BEC + + + + + F_E6B7F2644700 + F_354ED1961EDA + + + + + F_F5CC224041A9 + F_A6273D9180AB + + + + + F_AB48E0834BDA + F_F009F047846E + + + + + F_A0216E14BC97 + F_ACED4C55637F + + + + + F_0DD32BFE0A92 + F_02C27124E8CA + + + + + F_41C1825B657A + F_E44310094D4C + + + + + F_3F2CF6642E28 + F_7E5E0FD5074D + + + + + F_975807CC3902 + F_B4DFEC48DA99 + + + + + F_B3F568CC5872 + F_F24E2786F1DC + + + + + F_12D384D9D875 + F_9D2039D4613C + + + + + F_1C93454DFF35 + F_F45043F03189 + + + + + F_F86F666C9213 + F_AB7524B77FE3 + + + + + F_4AA74B2825D3 + F_553FD27EFDFF + + + + + F_AE55B75F8A03 + F_D6DCE208BABA + + + + + F_B4410AFDAF22 + F_60F5D91741D4 + + + + + F_3F1D3007B8D6 + F_3F1D3007B8D3 + + + + + F_C38660822897 + F_C38660822893 + + + + + F_7CF58ACF1167 + F_7CF58ACF1163 + + + + + F_CD5588301DD7 + F_CD5588301DD3 + + + + + F_A11BAA2D33B7 + F_A11BAA2D33B3 + + + + + F_AB7850D2D637 + F_AB7850D2D633 + + + + + F_D3CEC1A73F33 + F_DCD1DF24CE1C + + + + + F_EB24BA48997F + F_F2FAE7F66E5A + + + + + F_31FC9867E78C + F_4B33A7C22107 + + + + + F_2C9F348AC0B6 + F_8E90C1C23E39 + + + + + F_F3A38FE9EBE4 + F_9B53263EC377 + + + + + F_49F00E6E91DD + F_D8397852CC89 + + + + + F_D5C78A58C528 + F_447E5A36D94F + + + + + F_149970A1A305 + F_53C70BC61D47 + + + + + F_11D91612710E + F_A1D162E0FCF9 + + + + + F_651B48767404 + F_D995F7288818 + + + + + + F_125B566D5B71 + F_F785E9DB694C + + + F_69996786985B + F_63729C518C0D + + + + + + F_19E9A8484E4D + + F_A0B5B588B3FA + F_2714E85AD1A0 + + + + + + F_C719B927E7F6 + F_165A22CC9A2E + + + + + F_3431278969A0 + F_35297E0EBFF6 + + + + + F_83922928C10F + F_63D0258994E2 + + + + + F_F341F04E8270 + F_08C6BDCFC9D2 + + + + + F_D519ADF06E81 + F_EEC3C1F17AF0 + + + + + F_AE94F8FD236E + F_1FDB3C187DFD + + + + + F_A3375CB59CCE + F_981EE997431C + + + + + F_BFBCBB8D0D85 + F_BB1A68D46A1E + + + + + F_446D0F1F45DA + F_826663D93E33 + + + + + F_9857C16EDD28 + F_1E89B0D1799F + + + + + F_99F9D65EDB03 + F_128A78CFA4EB + + + + + F_C3AA994E80F6 + F_856EA5DF32C1 + + + + + F_7777F9445935 + F_5DA85E1C3028 + + + + + I_427006650__F_2B80F604E18C + I_427006650__F_18D7339F5770 + + + + + I_427006650__F_C08991A298B9 + I_427006650__F_18D7339F5770 + + + + + I_427006650__F_5506C5DC2F9E + I_427006650__F_18D7339F5770 + + + + + I_427006650__F_B467EDC51276 + I_427006650__F_18D7339F5770 + + + + + I_427006650__F_CFA04FA1FDE7 + I_427006650__F_18D7339F5770 + + + + + I_427006650__F_FD611AAE1DF1 + I_427006650__F_18D7339F5770 + + + + + I_1853139500__F_7786D07A62E6 + I_1853139500__F_D1F14F51CF02 + + + + + I_1853139500__F_F8482C56F0A8 + I_1853139500__F_D1F14F51CF02 + + + + + I_1853139500__F_F182290242AF + I_1853139500__F_D1F14F51CF02 + + + + + I_1853139500__F_C51AE01053F4 + I_1853139500__F_D1F14F51CF02 + + + + + I_1853139500__F_EA49CD79A5F6 + I_1853139500__F_D1F14F51CF02 + + + + + I_1853139500__F_11F1B9560FEB + I_1853139500__F_D1F14F51CF02 + + + + + I_4152228758__F_5BD784D05E7C + I_4152228758__F_02227FB25C19 + + + + + I_4152228758__F_6FC690FE5FF9 + I_4152228758__F_02227FB25C19 + + + + + I_4152228758__F_DB2FF447C180 + I_4152228758__F_02227FB25C19 + + + + + I_4152228758__F_14A305816B8D + I_4152228758__F_02227FB25C19 + + + + + I_4152228758__F_36EDB2880D18 + I_4152228758__F_02227FB25C19 + + + + + I_4152228758__F_F251F2917D3C + I_4152228758__F_02227FB25C19 + + + + + I_2155531008__F_D6EEDEF0629D + I_2155531008__F_9095FE23B24D + + + + + I_2155531008__F_5FC8B8B179E1 + I_2155531008__F_9095FE23B24D + + + + + I_2155531008__F_A7E52ADAD538 + I_2155531008__F_9095FE23B24D + + + + + I_2155531008__F_B2D0F5C303EB + I_2155531008__F_9095FE23B24D + + + + + I_2155531008__F_59B9A55047C9 + I_2155531008__F_9095FE23B24D + + + + + I_2155531008__F_513BBBAA6847 + I_2155531008__F_9095FE23B24D + + + + + I_505306787__F_4F64A86FE3A8 + I_505306787__F_29856BC41AC3 + + + + + I_505306787__F_99D87B833AD1 + I_505306787__F_29856BC41AC3 + + + + + I_505306787__F_AD4BE9D453AA + I_505306787__F_29856BC41AC3 + + + + + I_505306787__F_5E326EBCC6C6 + I_505306787__F_29856BC41AC3 + + + + + I_505306787__F_55BAEA238B37 + I_505306787__F_29856BC41AC3 + + + + + I_505306787__F_CA21B024C340 + I_505306787__F_29856BC41AC3 + + + + + I_1763274293__F_F1F502221F45 + I_1763274293__F_F06B81BF7FB9 + + + + + I_1763274293__F_0642A0BB0809 + I_1763274293__F_F06B81BF7FB9 + + + + + I_1763274293__F_ED16DF087DC0 + I_1763274293__F_F06B81BF7FB9 + + + + + I_1763274293__F_E551F2361C88 + I_1763274293__F_F06B81BF7FB9 + + + + + I_1763274293__F_3216ECF4340E + I_1763274293__F_F06B81BF7FB9 + + + + + I_1763274293__F_CA19EEC34A4B + I_1763274293__F_F06B81BF7FB9 + + + + + I_4027596687__F_876F7517B8A8 + I_4027596687__F_DCF6F754AEA6 + + + + + I_4027596687__F_D471ED411789 + I_4027596687__F_DCF6F754AEA6 + + + + + I_4027596687__F_03186FA42ED1 + I_4027596687__F_DCF6F754AEA6 + + + + + I_4027596687__F_30A142B1491E + I_4027596687__F_DCF6F754AEA6 + + + + + I_4027596687__F_F08840E7924A + I_4027596687__F_DCF6F754AEA6 + + + + + I_4027596687__F_81084B648336 + I_4027596687__F_DCF6F754AEA6 + + + + + I_2266435353__F_9A29D0C9C8F0 + I_2266435353__F_AA8D335AFDD2 + + + + + I_2266435353__F_02241DDDFF69 + I_2266435353__F_AA8D335AFDD2 + + + + + I_2266435353__F_1EA9B8A0DAC1 + I_2266435353__F_AA8D335AFDD2 + + + + + I_2266435353__F_703DF34D8BEB + I_2266435353__F_AA8D335AFDD2 + + + + + I_2266435353__F_F5687784410A + I_2266435353__F_AA8D335AFDD2 + + + + + I_2266435353__F_81AEF4E1A90E + I_2266435353__F_AA8D335AFDD2 + + + + + I_681279527__F_92A599EED7F6 + I_681279527__F_5B294D852562 + + + + + I_681279527__F_FFE81F89D790 + I_681279527__F_5B294D852562 + + + + + I_681279527__F_3DFA546F37B4 + I_681279527__F_5B294D852562 + + + + + I_681279527__F_525DAC6B8C4B + I_681279527__F_5B294D852562 + + + + + I_681279527__F_10506D296241 + I_681279527__F_5B294D852562 + + + + + I_681279527__F_A2188D13EF35 + I_681279527__F_5B294D852562 + + + + + I_1604104369__F_41B80CEAE49C + I_1604104369__F_EC4F2A12DA3F + + + + + I_1604104369__F_98ABA5237C7C + I_1604104369__F_EC4F2A12DA3F + + + + + I_1604104369__F_7415F0F8A2FF + I_1604104369__F_EC4F2A12DA3F + + + + + I_1604104369__F_E75B56A0CBDB + I_1604104369__F_EC4F2A12DA3F + + + + + I_1604104369__F_508856E7D1D1 + I_1604104369__F_EC4F2A12DA3F + + + + + I_1604104369__F_5F4008EE8D07 + I_1604104369__F_EC4F2A12DA3F + + + + + I_3331711243__F_A87BB690F475 + I_3331711243__F_30D093723E98 + + + + + I_3331711243__F_50A3F1C00E5E + I_3331711243__F_30D093723E98 + + + + + I_3331711243__F_64753EB48A78 + I_3331711243__F_30D093723E98 + + + + + I_3331711243__F_00ED66FBE181 + I_3331711243__F_30D093723E98 + + + + + I_3331711243__F_9656CA973E4B + I_3331711243__F_30D093723E98 + + + + + I_3331711243__F_5089FA4FDE56 + I_3331711243__F_30D093723E98 + + + + + I_2979189149__F_874040751A61 + I_2979189149__F_70C10F9B2ADF + + + + + I_2979189149__F_906023BCFA03 + I_2979189149__F_70C10F9B2ADF + + + + + I_2979189149__F_B8F753D0FB74 + I_2979189149__F_70C10F9B2ADF + + + + + I_2979189149__F_99CE499B0C94 + I_2979189149__F_70C10F9B2ADF + + + + + I_2979189149__F_84504D98B24E + I_2979189149__F_70C10F9B2ADF + + + + + I_2979189149__F_7CE6F5817681 + I_2979189149__F_70C10F9B2ADF + + + + + I_804668478__F_15EB9C7255F3 + I_804668478__F_775BB79130CA + + + + + I_804668478__F_322116B5A2EC + I_804668478__F_775BB79130CA + + + + + I_804668478__F_58BA651D87DC + I_804668478__F_775BB79130CA + + + + + I_804668478__F_7A4E30DEC5BF + I_804668478__F_775BB79130CA + + + + + I_804668478__F_E1508BFA50BF + I_804668478__F_775BB79130CA + + + + + I_804668478__F_252A85C0E107 + I_804668478__F_775BB79130CA + + + + + I_1492219048__F_F3BDABD6AD1B + I_1492219048__F_26B558FAE7E5 + + + + + I_1492219048__F_EF29EBFE312E + I_1492219048__F_26B558FAE7E5 + + + + + I_1492219048__F_4D4A5D83B4A1 + I_1492219048__F_26B558FAE7E5 + + + + + I_1492219048__F_BF025748249B + I_1492219048__F_26B558FAE7E5 + + + + + I_1492219048__F_103249CC7279 + I_1492219048__F_26B558FAE7E5 + + + + + I_1492219048__F_445AED118D01 + I_1492219048__F_26B558FAE7E5 + + + + + I_3254265106__F_5ABCAA20CD41 + I_3254265106__F_FA4767D603F6 + + + + + I_3254265106__F_471E3FA5E604 + I_3254265106__F_FA4767D603F6 + + + + + I_3254265106__F_E13307E04D66 + I_3254265106__F_FA4767D603F6 + + + + + I_3254265106__F_7F19AF9A162F + I_3254265106__F_FA4767D603F6 + + + + + I_3254265106__F_3CE06294D793 + I_3254265106__F_FA4767D603F6 + + + + + I_3254265106__F_1F462DFD4396 + I_3254265106__F_FA4767D603F6 + + + + + I_3070170500__F_E358F810915F + I_3070170500__F_3B278A1469FB + + + + + I_3070170500__F_8C3ACCD5AD2B + I_3070170500__F_3B278A1469FB + + + + + I_3070170500__F_49F05559B842 + I_3070170500__F_3B278A1469FB + + + + + I_3070170500__F_8C7A1BA5D828 + I_3070170500__F_3B278A1469FB + + + + + I_3070170500__F_2AC1A130CAC9 + I_3070170500__F_3B278A1469FB + + + + + I_3070170500__F_6B55F655DF57 + I_3070170500__F_3B278A1469FB + + + + + I_2397866899__F_7DC594B9F293 + I_2397866899__F_41D7A6BCCC20 + + + + + I_2397866899__F_8B1745569FBF + I_2397866899__F_41D7A6BCCC20 + + + + + I_2397866899__F_FC98C9D1BABC + I_2397866899__F_41D7A6BCCC20 + + + + + I_2397866899__F_ADEA50F38BCE + I_2397866899__F_41D7A6BCCC20 + + + + + I_2397866899__F_8CD195A6ACA0 + I_2397866899__F_41D7A6BCCC20 + + + + + I_2397866899__F_3E9A04BE6CA6 + I_2397866899__F_41D7A6BCCC20 + + + + + I_4192975621__F_CA0559DED043 + I_4192975621__F_6DDAC97F2897 + + + + + I_4192975621__F_35124CC25FE9 + I_4192975621__F_6DDAC97F2897 + + + + + I_4192975621__F_6A94FF163268 + I_4192975621__F_6DDAC97F2897 + + + + + I_4192975621__F_76E537798BA6 + I_4192975621__F_6DDAC97F2897 + + + + + I_4192975621__F_37196C1159D6 + I_4192975621__F_6DDAC97F2897 + + + + + I_4192975621__F_85516E6F8452 + I_4192975621__F_6DDAC97F2897 + + + + + I_1625483967__F_920A57B649ED + I_1625483967__F_73CEFA06C4F2 + + + + + I_1625483967__F_2E5A79B15C2C + I_1625483967__F_73CEFA06C4F2 + + + + + I_1625483967__F_9209830C5506 + I_1625483967__F_73CEFA06C4F2 + + + + + I_1625483967__F_FCB19AB856EC + I_1625483967__F_73CEFA06C4F2 + + + + + I_1625483967__F_9F4C694CD445 + I_1625483967__F_73CEFA06C4F2 + + + + + I_1625483967__F_EFFCDEF2A258 + I_1625483967__F_73CEFA06C4F2 + + + + + I_400939561__F_35AD26C0C0CC + I_400939561__F_A7FD98AEFD04 + + + + + I_400939561__F_ACE10ED1469B + I_400939561__F_A7FD98AEFD04 + + + + + I_400939561__F_A7C15E573187 + I_400939561__F_A7FD98AEFD04 + + + + + I_400939561__F_21DD401F7EE7 + I_400939561__F_A7FD98AEFD04 + + + + + I_400939561__F_009493651E55 + I_400939561__F_A7FD98AEFD04 + + + + + I_400939561__F_BBA725F548F9 + I_400939561__F_A7FD98AEFD04 + + + + + I_2306953098__F_F7D3FC81B1DD + I_2306953098__F_B10541C3355A + + + + + I_2306953098__F_CE640F54F965 + I_2306953098__F_B10541C3355A + + + + + I_2306953098__F_EAC28751CEC6 + I_2306953098__F_B10541C3355A + + + + + I_2306953098__F_C86222D9C13A + I_2306953098__F_B10541C3355A + + + + + I_2306953098__F_0B333A2987A8 + I_2306953098__F_B10541C3355A + + + + + I_2306953098__F_7FD814466E71 + I_2306953098__F_B10541C3355A + + + + + I_4270227228__F_0D7988EFB255 + I_4270227228__F_0B6F6276DF72 + + + + + I_4270227228__F_7D88E14FB6DD + I_4270227228__F_0B6F6276DF72 + + + + + I_4270227228__F_064DFD819A58 + I_4270227228__F_0B6F6276DF72 + + + + + I_4270227228__F_E02E5D5394D9 + I_4270227228__F_0B6F6276DF72 + + + + + I_4270227228__F_729D65A41724 + I_4270227228__F_0B6F6276DF72 + + + + + I_4270227228__F_95A2E33B9E04 + I_4270227228__F_0B6F6276DF72 + + + + + I_1737436838__F_EA054B7E9F82 + I_1737436838__F_F105A324BEB3 + + + + + I_1737436838__F_1E9F14BCFBA6 + I_1737436838__F_F105A324BEB3 + + + + + I_1737436838__F_D8950F63B5A5 + I_1737436838__F_F105A324BEB3 + + + + + I_1737436838__F_5ED87C837EA9 + I_1737436838__F_F105A324BEB3 + + + + + I_1737436838__F_F93996B4DA6E + I_1737436838__F_F105A324BEB3 + + + + + I_1737436838__F_89744EED9EE8 + I_1737436838__F_F105A324BEB3 + + + + + I_277356080__F_A8A1154629A7 + I_277356080__F_2E9E6A4C7857 + + + + + I_277356080__F_BE6A68647E76 + I_277356080__F_2E9E6A4C7857 + + + + + I_277356080__F_6FCC2B1B32CF + I_277356080__F_2E9E6A4C7857 + + + + + I_277356080__F_86A0C3EB25F9 + I_277356080__F_2E9E6A4C7857 + + + + + I_277356080__F_C0FDE0BD4155 + I_277356080__F_2E9E6A4C7857 + + + + + I_277356080__F_0B40691CE9FA + I_277356080__F_2E9E6A4C7857 + + + + + I_1263252765__F_35B1923B97F7 + I_1263252765__F_139AAC9E3EAF + + + + + I_1263252765__F_F7848DB5634A + I_1263252765__F_139AAC9E3EAF + + + + + I_1263252765__F_E34924B4EE83 + I_1263252765__F_139AAC9E3EAF + + + + + I_1263252765__F_40B8121660B7 + I_1263252765__F_139AAC9E3EAF + + + + + I_1263252765__F_E29A0C26754A + I_1263252765__F_139AAC9E3EAF + + + + + I_1263252765__F_F4A55A0FED43 + I_1263252765__F_139AAC9E3EAF + + + + + I_1011647883__F_79CA7D925109 + I_1011647883__F_A35E04AE4233 + + + + + I_1011647883__F_E343DE8BD2BA + I_1011647883__F_A35E04AE4233 + + + + + I_1011647883__F_103E6F39F653 + I_1011647883__F_A35E04AE4233 + + + + + I_1011647883__F_70DA8FF8D52E + I_1011647883__F_A35E04AE4233 + + + + + I_1011647883__F_B3345513A05B + I_1011647883__F_A35E04AE4233 + + + + + I_1011647883__F_5BE77C892686 + I_1011647883__F_A35E04AE4233 + + + + + I_2772816945__F_0826076E31F7 + I_2772816945__F_7D24B102DC3A + + + + + I_2772816945__F_F50F9FB3BD23 + I_2772816945__F_7D24B102DC3A + + + + + I_2772816945__F_1A58241F5F45 + I_2772816945__F_7D24B102DC3A + + + + + I_2772816945__F_E5D75D7E7786 + I_2772816945__F_7D24B102DC3A + + + + + I_2772816945__F_FB1F1AFBC25C + I_2772816945__F_7D24B102DC3A + + + + + I_2772816945__F_4A099539CC91 + I_2772816945__F_7D24B102DC3A + + + + + I_3527599271__F_ECC03F7551CD + I_3527599271__F_415F10FF5ED4 + + + + + I_3527599271__F_24A450FE0913 + I_3527599271__F_415F10FF5ED4 + + + + + I_3527599271__F_F3A9615D4D8A + I_3527599271__F_415F10FF5ED4 + + + + + I_3527599271__F_9E3A435B85D1 + I_3527599271__F_415F10FF5ED4 + + + + + I_3527599271__F_DE9B2C23D02F + I_3527599271__F_415F10FF5ED4 + + + + + I_3527599271__F_4C48EE17B478 + I_3527599271__F_415F10FF5ED4 + + + + + I_1277587716__F_C2733386ADF1 + I_1277587716__F_128C6FF34E22 + + + + + I_1277587716__F_A2E40A4A4913 + I_1277587716__F_128C6FF34E22 + + + + + I_1277587716__F_CA62A1C41AA7 + I_1277587716__F_128C6FF34E22 + + + + + I_1277587716__F_07C6E714BE09 + I_1277587716__F_128C6FF34E22 + + + + + I_1277587716__F_E38C0976C185 + I_1277587716__F_128C6FF34E22 + + + + + I_1277587716__F_B6718422D526 + I_1277587716__F_128C6FF34E22 + + + + + I_992035218__F_BE225B20A8C1 + I_992035218__F_936A4CBD3788 + + + + + I_992035218__F_2B2DF8A846D7 + I_992035218__F_936A4CBD3788 + + + + + I_992035218__F_2DF5EF4684D5 + I_992035218__F_936A4CBD3788 + + + + + I_992035218__F_CF4436881B68 + I_992035218__F_936A4CBD3788 + + + + + I_992035218__F_F9170BE90FD6 + I_992035218__F_936A4CBD3788 + + + + + I_992035218__F_F69C20050476 + I_992035218__F_936A4CBD3788 + + + + + I_2720534568__F_DEE99FC61FD6 + I_2720534568__F_90E0DD076AA2 + + + + + I_2720534568__F_E7AD6B3416AD + I_2720534568__F_90E0DD076AA2 + + + + + I_2720534568__F_A47FE6A371D7 + I_2720534568__F_90E0DD076AA2 + + + + + I_2720534568__F_6D85F49F3CBD + I_2720534568__F_90E0DD076AA2 + + + + + I_2720534568__F_4A61ABA27FA4 + I_2720534568__F_90E0DD076AA2 + + + + + I_2720534568__F_E46F9B5D5C8A + I_2720534568__F_90E0DD076AA2 + + + + + I_3576635582__F_46FBC70592A1 + I_3576635582__F_14BEA1E4D5E8 + + + + + I_3576635582__F_F3BF010EA033 + I_3576635582__F_14BEA1E4D5E8 + + + + + I_3576635582__F_3100FA407737 + I_3576635582__F_14BEA1E4D5E8 + + + + + I_3576635582__F_4F17C266D0A7 + I_3576635582__F_14BEA1E4D5E8 + + + + + I_3576635582__F_5372EB8CD674 + I_3576635582__F_14BEA1E4D5E8 + + + + + I_3576635582__F_F0E8D097C774 + I_3576635582__F_14BEA1E4D5E8 + + + + + I_318152588__F_43EFD0FD23DA + I_318152588__F_BE8AE4C92924 + + + + + I_318152588__F_EFE003123081 + I_318152588__F_BE8AE4C92924 + + + + + I_318152588__F_8AFA74871032 + I_318152588__F_BE8AE4C92924 + + + + + I_318152588__F_0B73473BA5BC + I_318152588__F_BE8AE4C92924 + + + + + I_318152588__F_3EC4A84D9E48 + I_318152588__F_BE8AE4C92924 + + + + + I_318152588__F_F5D38B2D0DFE + I_318152588__F_BE8AE4C92924 + + + + + I_1710337818__F_09F895084D6A + I_1710337818__F_F655F9577FD4 + + + + + I_1710337818__F_5F90335A42E2 + I_1710337818__F_F655F9577FD4 + + + + + I_1710337818__F_F6A392D3EC16 + I_1710337818__F_F655F9577FD4 + + + + + I_1710337818__F_C01C6156953E + I_1710337818__F_F655F9577FD4 + + + + + I_1710337818__F_EB2822050EB4 + I_1710337818__F_F655F9577FD4 + + + + + I_1710337818__F_FC479DFBF648 + I_1710337818__F_F655F9577FD4 + + + + + I_4244176544__F_A04C58D5A1DC + I_4244176544__F_CBE82CF42724 + + + + + I_4244176544__F_3A08236A727B + I_4244176544__F_CBE82CF42724 + + + + + I_4244176544__F_4BE3ED9C9EC5 + I_4244176544__F_CBE82CF42724 + + + + + I_4244176544__F_9128C55FFFA5 + I_4244176544__F_CBE82CF42724 + + + + + I_4244176544__F_8CCD2BD1E014 + I_4244176544__F_CBE82CF42724 + + + + + I_4244176544__F_E6D34E9CEE4D + I_4244176544__F_CBE82CF42724 + + + + + I_2348797494__F_A426A21B87B2 + I_2348797494__F_7C3F067CCB4C + + + + + I_2348797494__F_223ECA4377D7 + I_2348797494__F_7C3F067CCB4C + + + + + I_2348797494__F_0BB0D998AF9C + I_2348797494__F_7C3F067CCB4C + + + + + I_2348797494__F_A97011794482 + I_2348797494__F_7C3F067CCB4C + + + + + I_2348797494__F_4D2FC9BFE6AA + I_2348797494__F_7C3F067CCB4C + + + + + I_2348797494__F_5D483196EFD0 + I_2348797494__F_7C3F067CCB4C + + + + + I_362503061__F_9EA07E1FFF27 + I_362503061__F_BCDCABB81949 + + + + + I_362503061__F_8C186379B2C2 + I_362503061__F_BCDCABB81949 + + + + + I_362503061__F_CAB9F865ABC4 + I_362503061__F_BCDCABB81949 + + + + + I_362503061__F_4EADC878C67F + I_362503061__F_BCDCABB81949 + + + + + I_362503061__F_295750E43AAB + I_362503061__F_BCDCABB81949 + + + + + I_362503061__F_3B07A9D3E8C9 + I_362503061__F_BCDCABB81949 + + + + + I_1654418179__F_FB33AAE858BB + I_1654418179__F_AD2EF0918D4B + + + + + I_1654418179__F_7CA4E4DA3DCF + I_1654418179__F_AD2EF0918D4B + + + + + I_1654418179__F_DD6578F3E0F1 + I_1654418179__F_AD2EF0918D4B + + + + + I_1654418179__F_8FCA284312E6 + I_1654418179__F_AD2EF0918D4B + + + + + I_1654418179__F_C3A927F7B077 + I_1654418179__F_AD2EF0918D4B + + + + + I_1654418179__F_4943848A10A9 + I_1654418179__F_AD2EF0918D4B + + + + + I_4220861113__F_B2BDEDDACC30 + I_4220861113__F_2A26237F8ABD + + + + + I_4220861113__F_898ADE851FD7 + I_4220861113__F_2A26237F8ABD + + + + + I_4220861113__F_6C591236CF0A + I_4220861113__F_2A26237F8ABD + + + + + I_4220861113__F_DF8FDADFF584 + I_4220861113__F_2A26237F8ABD + + + + + I_4220861113__F_06F8D93795D5 + I_4220861113__F_2A26237F8ABD + + + + + I_4220861113__F_A30F5CDE00FA + I_4220861113__F_2A26237F8ABD + + + + + I_2358381103__F_7D145533C3A4 + I_2358381103__F_6E8B90B84D57 + + + + + I_2358381103__F_40861A879394 + I_2358381103__F_6E8B90B84D57 + + + + + I_2358381103__F_3FCDC3F789B1 + I_2358381103__F_6E8B90B84D57 + + + + + I_2358381103__F_50DCF3732F4A + I_2358381103__F_6E8B90B84D57 + + + + + I_2358381103__F_C5C40A025524 + I_2358381103__F_6E8B90B84D57 + + + + + I_2358381103__F_47BA7F36CF69 + I_2358381103__F_6E8B90B84D57 + + + + + I_320491705__F_B09F7FD7B89C + I_320491705__F_C4E83C73F73D + + + + + I_320491705__F_F4DEC5920E5A + I_320491705__F_C4E83C73F73D + + + + + I_320491705__F_2342F97447BD + I_320491705__F_C4E83C73F73D + + + + + I_320491705__F_570D1D127DF0 + I_320491705__F_C4E83C73F73D + + + + + I_320491705__F_D7CB8B36991E + I_320491705__F_C4E83C73F73D + + + + + I_320491705__F_1A7A3EDEE4F2 + I_320491705__F_C4E83C73F73D + + + + + I_1679646767__F_0A3553580295 + I_1679646767__F_774238AD1F26 + + + + + I_1679646767__F_FC093C5C7C83 + I_1679646767__F_774238AD1F26 + + + + + I_1679646767__F_D624A4060885 + I_1679646767__F_774238AD1F26 + + + + + I_1679646767__F_4BA90C522521 + I_1679646767__F_774238AD1F26 + + + + + I_1679646767__F_33AE1D252636 + I_1679646767__F_774238AD1F26 + + + + + I_1679646767__F_3630C19C4F9F + I_1679646767__F_774238AD1F26 + + + + + I_4245959061__F_ECDA88D40676 + I_4245959061__F_ED35FFF734D5 + + + + + I_4245959061__F_BFCD9F7E58F9 + I_4245959061__F_ED35FFF734D5 + + + + + I_4245959061__F_23B6221801FD + I_4245959061__F_ED35FFF734D5 + + + + + I_4245959061__F_886504C8008F + I_4245959061__F_ED35FFF734D5 + + + + + I_4245959061__F_B9BDF5827E0F + I_4245959061__F_ED35FFF734D5 + + + + + I_4245959061__F_10887C36EED3 + I_4245959061__F_ED35FFF734D5 + + + + + I_3824206844__F_E845A0A8A50D + I_3824206844__F_A0CE708BD560 + + + + + I_3824206844__F_9C339C1A84E6 + I_3824206844__F_A0CE708BD560 + + + + + I_3824206844__F_C5FCE4D99852 + I_3824206844__F_A0CE708BD560 + + + + + I_3824206844__F_948D23F5645A + I_3824206844__F_A0CE708BD560 + + + + + I_3824206844__F_725ED57E785C + I_3824206844__F_A0CE708BD560 + + + + + I_3824206844__F_93A2E5D2A6C9 + I_3824206844__F_A0CE708BD560 + + + + + I_2499277674__F_1EF10A6BBA42 + I_2499277674__F_D959CA99D93A + + + + + I_2499277674__F_83DF97DED842 + I_2499277674__F_D959CA99D93A + + + + + I_2499277674__F_464535D9C4A3 + I_2499277674__F_D959CA99D93A + + + + + I_2499277674__F_CB5205C1CB23 + I_2499277674__F_D959CA99D93A + + + + + I_2499277674__F_AF1AB73C3E6C + I_2499277674__F_D959CA99D93A + + + + + I_2499277674__F_08A81ABB5FD9 + I_2499277674__F_D959CA99D93A + + + + + I_234791632__F_510F6170C700 + I_234791632__F_1DB333E2B5B6 + + + + + I_234791632__F_4D1613674C73 + I_234791632__F_1DB333E2B5B6 + + + + + I_234791632__F_4B51E9E1C64B + I_234791632__F_1DB333E2B5B6 + + + + + I_234791632__F_48BFAE4EE5F2 + I_234791632__F_1DB333E2B5B6 + + + + + I_234791632__F_7D0A5226A3CC + I_234791632__F_1DB333E2B5B6 + + + + + I_234791632__F_F1F9237C3986 + I_234791632__F_1DB333E2B5B6 + + + + + I_2063176262__F_0714A3272941 + I_2063176262__F_65725364AAD4 + + + + + I_2063176262__F_AA2B87268862 + I_2063176262__F_65725364AAD4 + + + + + I_2063176262__F_046D0F6C2189 + I_2063176262__F_65725364AAD4 + + + + + I_2063176262__F_8C277DF617FA + I_2063176262__F_65725364AAD4 + + + + + I_2063176262__F_BB3DB6B7AA0D + I_2063176262__F_65725364AAD4 + + + + + I_2063176262__F_C5021F0861CC + I_2063176262__F_65725364AAD4 + + + + + I_3930492887__F_01CF4CADEFF1 + I_3930492887__F_463B6610B29E + + + + + I_3930492887__F_C72CA20C4510 + I_3930492887__F_463B6610B29E + + + + + I_3930492887__F_4015819D9818 + I_3930492887__F_463B6610B29E + + + + + I_3930492887__F_0435549C5DA2 + I_3930492887__F_463B6610B29E + + + + + I_3930492887__F_1CEAB277C46B + I_3930492887__F_463B6610B29E + + + + + I_3930492887__F_104158077B30 + I_3930492887__F_463B6610B29E + + + + + I_2638331713__F_A0487B1665F4 + I_2638331713__F_A461FE62EF40 + + + + + I_2638331713__F_E0F224DC1F81 + I_2638331713__F_A461FE62EF40 + + + + + I_2638331713__F_B54D47FCD3F0 + I_2638331713__F_A461FE62EF40 + + + + + I_2638331713__F_2CC0B53B0F30 + I_2638331713__F_A461FE62EF40 + + + + + I_2638331713__F_92820982C23A + I_2638331713__F_A461FE62EF40 + + + + + I_2638331713__F_FA89F047E4B1 + I_2638331713__F_A461FE62EF40 + + + + + I_59350957__F_19254B841BC6 + I_59350957__F_35040BC0599E + + + + + I_59350957__F_84BC02FFDB7D + I_59350957__F_35040BC0599E + + + + + I_59350957__F_FAB3D70AFE3D + I_59350957__F_35040BC0599E + + + + + I_59350957__F_FF3158DB54DC + I_59350957__F_35040BC0599E + + + + + I_59350957__F_DD5E57611DDA + I_59350957__F_35040BC0599E + + + + + I_59350957__F_CA3AFEB67C90 + I_59350957__F_35040BC0599E + + + + + I_1955508027__F_E84F1FCD38F9 + I_1955508027__F_3FB9F13D8663 + + + + + I_1955508027__F_C8D7C93B2401 + I_1955508027__F_3FB9F13D8663 + + + + + I_1955508027__F_43681A7D53B5 + I_1955508027__F_3FB9F13D8663 + + + + + I_1955508027__F_AC6E8AFE182E + I_1955508027__F_3FB9F13D8663 + + + + + I_1955508027__F_4EE52869DE5E + I_1955508027__F_3FB9F13D8663 + + + + + I_1955508027__F_559AAC6E3C4E + I_1955508027__F_3FB9F13D8663 + + + + + I_921795804__F_6A196AE90A60 + I_921795804__F_766AB02F83E4 + + + + + I_921795804__F_F2586FB5B1F8 + I_921795804__F_766AB02F83E4 + + + + + I_921795804__F_963B46E6E975 + I_921795804__F_766AB02F83E4 + + + + + I_921795804__F_C420477AE336 + I_921795804__F_766AB02F83E4 + + + + + I_921795804__F_95A11237E1D8 + I_921795804__F_766AB02F83E4 + + + + + I_921795804__F_FFA263A33E50 + I_921795804__F_766AB02F83E4 + + + + + I_2952277350__F_72AC40AB2032 + I_2952277350__F_6B6C5ADF86C5 + + + + + I_2952277350__F_955C06E21B49 + I_2952277350__F_6B6C5ADF86C5 + + + + + I_2952277350__F_57BF390A5706 + I_2952277350__F_6B6C5ADF86C5 + + + + + I_2952277350__F_D5AEF7584334 + I_2952277350__F_6B6C5ADF86C5 + + + + + I_2952277350__F_BA434630EDD8 + I_2952277350__F_6B6C5ADF86C5 + + + + + I_2952277350__F_6CE07B16ED6F + I_2952277350__F_6B6C5ADF86C5 + + + + + I_3640598000__F_A6A572B127AE + I_3640598000__F_7FFE179269DB + + + + + I_3640598000__F_F849F27DA699 + I_3640598000__F_7FFE179269DB + + + + + I_3640598000__F_F26BD247035D + I_3640598000__F_7FFE179269DB + + + + + I_3640598000__F_EABFE77D506B + I_3640598000__F_7FFE179269DB + + + + + I_3640598000__F_F702773F74F0 + I_3640598000__F_7FFE179269DB + + + + + I_3640598000__F_BD0A89A188D7 + I_3640598000__F_7FFE179269DB + + + + + I_1184598099__F_C554A8ABFA14 + I_1184598099__F_FC9BF5094708 + + + + + I_1184598099__F_C02BAFC79310 + I_1184598099__F_FC9BF5094708 + + + + + I_1184598099__F_465EF9F9BF2E + I_1184598099__F_FC9BF5094708 + + + + + I_1184598099__F_407B73050C82 + I_1184598099__F_FC9BF5094708 + + + + + I_1184598099__F_14A9776E0731 + I_1184598099__F_FC9BF5094708 + + + + + I_1184598099__F_58B0A73B0D55 + I_1184598099__F_FC9BF5094708 + + + + + I_832354501__F_F22F1C6B6E10 + I_832354501__F_F62D67B040B0 + + + + + I_832354501__F_A9E1161C918C + I_832354501__F_F62D67B040B0 + + + + + I_832354501__F_E195DCE4204A + I_832354501__F_F62D67B040B0 + + + + + I_832354501__F_3E9F32757EB7 + I_832354501__F_F62D67B040B0 + + + + + I_832354501__F_094228167133 + I_832354501__F_F62D67B040B0 + + + + + I_832354501__F_865C6179C669 + I_832354501__F_F62D67B040B0 + + + + + I_2828396927__F_1350DC9987FD + I_2828396927__F_E100E5EB5CF7 + + + + + I_2828396927__F_DB6D23D24163 + I_2828396927__F_E100E5EB5CF7 + + + + + I_2828396927__F_84B10C6963DB + I_2828396927__F_E100E5EB5CF7 + + + + + I_2828396927__F_FB78DDA031A4 + I_2828396927__F_E100E5EB5CF7 + + + + + I_2828396927__F_14CC27999919 + I_2828396927__F_E100E5EB5CF7 + + + + + I_2828396927__F_711B3B68C6EF + I_2828396927__F_E100E5EB5CF7 + + + + + + + From 83b6b77613dcbe3135b19ba501c327a03623a613 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 14:40:52 +0200 Subject: [PATCH 184/257] feat: Visualizer can now export to pdf! --- .gitignore | 1 + build.gradle | 1 + .../AVisualizeFeatureModelStats.java | 163 +++++++++++++++++- .../model/analysis/visualization/Testtmp.java | 8 +- 4 files changed, 160 insertions(+), 13 deletions(-) diff --git a/.gitignore b/.gitignore index b76fe34e..308f9222 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,4 @@ /.settings/ /bin/ /model_invalidInput.dot +/export.pdf diff --git a/build.gradle b/build.gradle index 1a905573..14214654 100644 --- a/build.gradle +++ b/build.gradle @@ -10,6 +10,7 @@ dependencies { implementation 'org.yaml:snakeyaml:2.5' implementation 'tools.jackson.dataformat:jackson-dataformat-csv:3.0.0' implementation 'org.knowm.xchart:xchart:3.8.8' + implementation 'de.rototor.pdfbox:graphics2d:3.0.5' } license { diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index e1795f11..5bb68202 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -5,11 +5,18 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; +import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.util.Matrix; import org.knowm.xchart.PieChart; import org.knowm.xchart.PieChartBuilder; import org.knowm.xchart.SwingWrapper; import org.knowm.xchart.internal.chartpart.Chart; +import java.awt.geom.AffineTransform; +import java.io.IOException; import java.util.*; import java.util.stream.Collectors; @@ -26,8 +33,8 @@ public abstract class AVisualizeFeatureModelStats { protected LinkedHashMap> analysisTreeData; final protected ArrayList> charts; - private Integer width = 800; - private Integer height = 600; + private Integer chartWidth = 800; + private Integer chartHeight = 600; public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { this.analysisTree = analysisTree; @@ -41,19 +48,19 @@ public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { } public Integer getWidth() { - return this.width; + return this.chartWidth; } public void setWidth(Integer width) { - this.width = width; + this.chartWidth = width; } public Integer getHeight() { - return this.height; + return this.chartHeight; } public void setHeight(Integer height) { - this.height = height; + this.chartHeight = height; } /** @@ -76,6 +83,10 @@ private boolean chartsAreEmptyDisplay() { return chartsAreEmpty("Cannot display chart: "); } + private boolean chartsAreEmptyPDF() { + return chartsAreEmpty("Cannot export chart to PDF: "); + } + /** * {@return String key used to fetch data from the Analysis Tree.} */ @@ -198,8 +209,11 @@ public void displayChart() { * Creates a live preview pop-up window of an internally generated chart, fetched by index. */ public void displayChart (Integer index) { - if (chartsAreEmptyDisplay()) {return;} - this.displayChart(this.charts.get(index)); + try { + this.displayChart(this.charts.get(index)); + } catch (IndexOutOfBoundsException e) { + FeatJAR.log().error("Unable to fetch chart with index "+ index + ": " + e); + } } /** @@ -213,6 +227,137 @@ public void displayAllCharts() { } } - // TODO pdf export + /** + * Takes an existing PDF document and adds a new page with the specified chart on it. + * @param chart the chart that will be added on its own page + * @param document existing PDF document + * @return 0 on success, 1 on IOException + */ + private int exportChartToPDF(Chart chart, PDDocument document) { + PDPage page = new PDPage(); + document.addPage(page); + + // Create a PdfBoxGraphics2D object and draw the chart into it + PdfBoxGraphics2D graphics; + try { + graphics = new PdfBoxGraphics2D(document, chartWidth, chartHeight); + } catch (IOException e) { + FeatJAR.log().error("PDF Exporter could not create PdfBoxGraphics2D object: " + e); + return 1; + } + chart.paint(graphics, chartWidth, chartHeight); + graphics.dispose(); + + // transforms the chart so it fits on the page, then draws it + try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) { + Matrix transformationMatrix = getPDFCenteringMatrix(page); + contentStream.transform(transformationMatrix); + contentStream.drawForm(graphics.getXFormObject()); + } catch (IOException e) { + FeatJAR.log().error("PDF Exporter failed to add a chart to its PDF page: " + e); + return 1; + } + + return 0; + } + + /** + * Creates a PDF document with a single page featuring the specified chart. + * @param chart chart that will be put on the page + * @param path full path to the destination file. Does not check whether you specified an extension. + * @return 0 on success, 1 otherwise. + */ + public int exportChartToPDF(Chart chart, String path) { + return exportAllChartsToPDF(Collections.singletonList(chart), path); + } + + /** + * Creates a PDF document with a single page featuring the specified chart. + * @param index index to retrieve a chart form the internal chart list + * @param path full path to the destination file. Does not check whether you specified an extension. + * @return 0 on success, 1 on general errors, 2 if there are no internal charts to use. + */ + public int exportChartToPDF(Integer index, String path) { + if (chartsAreEmptyDisplay()) {return 2;} + try { + return exportChartToPDF(charts.get(index), path); + } catch (IndexOutOfBoundsException e) { + FeatJAR.log().error("Unable to fetch chart with index "+ index + ": " + e); + return 1; + } + } + + /** + * Creates a PDF document with a single page featuring the first chart from the internal list. + * @param path full path to the destination file. Does not check whether you specified an extension. + * @return 0 on success, 1 on general errors, 2 if there are no internal charts to use. + */ + public int exportChartToPDF(String path) { + if (chartsAreEmptyDisplay()) {return 2;} + return exportChartToPDF(charts.get(0), path); + } + + /** + * Creates a new PDF document and fills it with pages that each feature one chart from the list + * @param charts any list of charts + * @param path full path to the destination file. Does not check whether you specified an extension. + * @return 0 on success, 1 otherwise + */ + public int exportAllChartsToPDF(List> charts, String path) { + try (PDDocument document = new PDDocument()) { + + int iExitCode; + for (Chart chart : charts) { + iExitCode = exportChartToPDF(chart, document); + if (iExitCode != 0) { + return 1; + } + } + document.save(path); + return 0; + + } catch (IOException e) { + + FeatJAR.log().error(e); + return 1; + + } + } + + /** + * Creates a new PDF document and fills it with pages that each feature one chart from the list + * @param path full path to the destination file. Does not check whether you specified an extension. + * @return 0 on success, 1 on general errors, 2 if there are no internal charts to use. + */ + public int exportAllChartsToPDF(String path) { + if (chartsAreEmptyDisplay()) {return 2;} + return exportAllChartsToPDF(charts, path); + } + + /** + * @param page + * {@return transformation matrix that scales a given chart to fill the page and be centered} + */ + private Matrix getPDFCenteringMatrix(PDPage page) { + float pageWidth = page.getMediaBox().getWidth(); + float pageHeight = page.getMediaBox().getHeight(); + + // calculate scale to fit chart to page (preserving aspect ratio) + float scaleX = pageWidth / chartWidth; + float scaleY = pageHeight / chartHeight; + float minScale = Math.min(scaleX, scaleY); + + // coordinates for centering chart + float translateX = (pageWidth - chartWidth * minScale) / 2; + float translateY = (pageHeight - chartHeight * minScale) / 2; + + // create transform matrix with scale and translation + AffineTransform at = new AffineTransform(); // affine transformations preserve distance ratios + at.translate(translateX, translateY); + at.scale(minScale, minScale); + + return new Matrix(at); + } + } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java index 83ba71ef..38f85c50 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java @@ -9,7 +9,6 @@ import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.cli.PrintStatistics; -import de.featjar.feature.model.io.FeatureModelFormats; import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; @@ -90,7 +89,7 @@ public static FeatureModel generateMediumTree() { return featureModel; } - public static void main(String[] args) { + public static void main(String[] args) throws Exception { PrintStatistics printStatistics = new PrintStatistics(); LinkedHashMap map = printStatistics.collectStats( generateMediumTree(), @@ -109,11 +108,12 @@ public static void main(String[] args) { VisualizeGroupDistribution viz = new VisualizeGroupDistribution(bigAnalysisTree); - //VisualizeConstraintOperatorDistribution viz = new VisualizeConstraintOperatorDistribution(bigAnalysisTree); + VisualizeConstraintOperatorDistribution viz2 = new VisualizeConstraintOperatorDistribution(bigAnalysisTree); //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(createDefaultTree()); //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(generateEmptyTree()); - viz.displayChart(); + //viz.displayChart(); + viz2.exportChartToPDF(99, "export.pdf"); } } From 216eae587678b274e82e3a10ba79337bc51e6780 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 15 Oct 2025 14:56:48 +0200 Subject: [PATCH 185/257] feat: finished computation uniformity. test: added corresponding test. refactor: changed return type and format of compute featurecounter --- .../ComputeDistributionFeatureSelections.java | 24 +- .../model/analysis/ComputeFeatureCounter.java | 90 ++++--- .../analysis/ComputeNumberConfigurations.java | 24 +- .../analysis/ComputeNumberVariables.java | 24 +- .../model/analysis/ComputeUniformity.java | 193 +++++++++----- .../model/transformer/ComputeFormula.java | 6 +- .../model/analysis/SamplePropertiesTest.java | 235 +++++++++++------- 7 files changed, 378 insertions(+), 218 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java b/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java index c31d83f2..1e0f4184 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java @@ -1,22 +1,22 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-formula. + * This file is part of FeatJAR-feature-model. * - * formula is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * formula is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with formula. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ package de.featjar.feature.model.analysis; @@ -30,18 +30,24 @@ import java.util.HashMap; import java.util.List; +/** + * Compute how often features are selected, deselected, or undefined. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class ComputeDistributionFeatureSelections extends AComputation> { - protected static final Dependency BOOLEAN_ASSIGMENT_LIST = + protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); - public ComputeDistributionFeatureSelections(IComputation booleanAssigmentList) { - super(booleanAssigmentList); + public ComputeDistributionFeatureSelections(IComputation booleanAssignmentList) { + super(booleanAssignmentList); } @Override public Result> compute(List dependencyList, Progress progress) { - BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGNMENT_LIST.get(dependencyList); HashMap selectionDistribution = new HashMap(); selectionDistribution.put("selected", 0); selectionDistribution.put("deselected", 0); diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java index 7976e8d4..9eb2ad00 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java @@ -1,22 +1,22 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-formula. + * This file is part of FeatJAR-feature-model. * - * formula is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * formula is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with formula. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ package de.featjar.feature.model.analysis; @@ -27,11 +27,17 @@ import de.featjar.base.data.Result; import de.featjar.formula.assignment.BooleanAssignment; import de.featjar.formula.assignment.BooleanAssignmentList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -public class ComputeFeatureCounter extends AComputation>> { +/** + * Compute how often features are selected, deselected, or undefined per feature. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ +public class ComputeFeatureCounter extends AComputation> { protected static final Dependency BOOLEAN_ASSIGMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); @@ -41,53 +47,69 @@ public ComputeFeatureCounter(IComputation booleanAssigmen } @Override - public Result>> compute(List dependencyList, Progress progress) { + public Result> compute(List dependencyList, Progress progress) { BooleanAssignmentList booleanAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); - HashMap> featureCounter = new HashMap>(); - - // for(String feature : booleanAssignmentList.getVariableMap().getVariableNames()) { - // featureCounter.put(feature,0); - // } + LinkedHashMap featureCounter = new LinkedHashMap(); for (int index : booleanAssignmentList.getVariableMap().getIndices()) { - HashMap tempMap = new HashMap(); - tempMap.put("selected", 0); - tempMap.put("deselected", 0); - tempMap.put("undefined", 0); - featureCounter.put(index, tempMap); + featureCounter.put(index + "_selected", 0); + featureCounter.put(index + "_deselected", 0); + featureCounter.put(index + "_undefined", 0); } for (BooleanAssignment assignment : booleanAssignmentList.getAll()) { for (int index : booleanAssignmentList.getVariableMap().getIndices()) { if (assignment.contains(index)) { - featureCounter - .get(index) - .replace("selected", featureCounter.get(index).get("selected") + 1); + featureCounter.replace(index + "_selected", featureCounter.get(index + "_selected") + 1); } else if (assignment.containsAnyNegated(index)) { - featureCounter - .get(index) - .replace("deselected", featureCounter.get(index).get("deselected") + 1); + featureCounter.replace(index + "_deselected", featureCounter.get(index + "_deselected") + 1); } else { - featureCounter - .get(index) - .replace("undefined", featureCounter.get(index).get("undefined") + 1); + featureCounter.replace(index + "_undefined", featureCounter.get(index + "_undefined") + 1); } } } - HashMap> featureNameCounter = new HashMap>(); + LinkedHashMap featureNameCounter = new LinkedHashMap(); - for (Map.Entry> entry : featureCounter.entrySet()) { - if (booleanAssignmentList.getVariableMap().get(entry.getKey()).isPresent()) { + for (Map.Entry entry : featureCounter.entrySet()) { + if (entry.getKey().contains("_selected") + && booleanAssignmentList + .getVariableMap() + .get(Integer.valueOf(entry.getKey().replace("_selected", ""))) + .isPresent()) { + featureNameCounter.put( + booleanAssignmentList + .getVariableMap() + .get(Integer.valueOf(entry.getKey().replace("_selected", ""))) + .get() + + "_selected", + entry.getValue()); + } else if (entry.getKey().contains("_deselected") + && booleanAssignmentList + .getVariableMap() + .get(Integer.valueOf(entry.getKey().replace("_deselected", ""))) + .isPresent()) { featureNameCounter.put( booleanAssignmentList - .getVariableMap() - .get(entry.getKey()) - .get(), + .getVariableMap() + .get(Integer.valueOf(entry.getKey().replace("_deselected", ""))) + .get() + + "_deselected", + entry.getValue()); + } else if (entry.getKey().contains("_undefined") + && booleanAssignmentList + .getVariableMap() + .get(Integer.valueOf(entry.getKey().replace("_undefined", ""))) + .isPresent()) { + featureNameCounter.put( + booleanAssignmentList + .getVariableMap() + .get(Integer.valueOf(entry.getKey().replace("_undefined", ""))) + .get() + + "_undefined", entry.getValue()); } } - return Result.of(featureNameCounter); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java index 02f38763..ea4a6b74 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java @@ -1,22 +1,22 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-formula. + * This file is part of FeatJAR-feature-model. * - * formula is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * formula is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with formula. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ package de.featjar.feature.model.analysis; @@ -28,18 +28,24 @@ import de.featjar.formula.assignment.BooleanAssignmentList; import java.util.List; +/** + * Compute how many assignments are in the assignmentList + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class ComputeNumberConfigurations extends AComputation { - protected static final Dependency BOOLEAN_ASSIGMENT_LIST = + protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); - public ComputeNumberConfigurations(IComputation booleanAssigmentList) { - super(booleanAssigmentList); + public ComputeNumberConfigurations(IComputation booleanAssignmentList) { + super(booleanAssignmentList); } @Override public Result compute(List dependencyList, Progress progress) { - BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGNMENT_LIST.get(dependencyList); return Result.of(booleanAssigmenAssignmentList.getAll().size()); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java index 0e54eced..9375cb31 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java @@ -1,22 +1,22 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-formula. + * This file is part of FeatJAR-feature-model. * - * formula is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * formula is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with formula. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ package de.featjar.feature.model.analysis; @@ -28,18 +28,24 @@ import de.featjar.formula.assignment.BooleanAssignmentList; import java.util.List; +/** + * Compute how many Variables are in the assignmentList + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class ComputeNumberVariables extends AComputation { - protected static final Dependency BOOLEAN_ASSIGMENT_LIST = + protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); - public ComputeNumberVariables(IComputation booleanAssigmentList) { - super(booleanAssigmentList); + public ComputeNumberVariables(IComputation booleanAssignmentList) { + super(booleanAssignmentList); } @Override public Result compute(List dependencyList, Progress progress) { - BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGNMENT_LIST.get(dependencyList); return Result.of(booleanAssigmenAssignmentList .getVariableMap() .getVariableNames() diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java index bc2e5ff8..3df03451 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -1,18 +1,34 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.analysis; -import java.math.BigInteger; -import java.util.HashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map.Entry; - +import de.featjar.analysis.javasmt.computation.ComputeSatisfiability; +import de.featjar.analysis.javasmt.computation.ComputeSolutionCount; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Computations; import de.featjar.base.computation.Dependency; import de.featjar.base.computation.IComputation; import de.featjar.base.computation.Progress; import de.featjar.base.data.Result; -import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.transformer.ComputeFormula; import de.featjar.formula.VariableMap; import de.featjar.formula.assignment.BooleanAssignment; @@ -21,84 +37,123 @@ import de.featjar.formula.computation.ComputeNNFFormula; import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.And; +import de.featjar.formula.structure.connective.Not; import de.featjar.formula.structure.connective.Reference; import de.featjar.formula.structure.predicate.Literal; -import de.featjar.formula.structure.term.value.Variable; -import de.featjar.feature.model.IFeatureModel; -import de.featjar.analysis.javasmt.computation.ComputeSatisfiability; -import de.featjar.analysis.javasmt.computation.ComputeSolutionCount; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; -public class ComputeUniformity extends AComputation> { +/** + * Computes how often features are selected compared to their general selection in their feature model. + * This takes only valid configurations into account. + * + * BOOLEAN_ASSIGNMENT_LIST - current assignment + * FEATURE_MODEL - feature model + * ANALYSIS - when false return the full count of valid assignments in both AssignmentSample and the FeatureModel per feature, + * else the difference between the commonality of the AssignmentSample and the FeatureModel per feature. + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ +public class ComputeUniformity extends AComputation> { - protected static final Dependency BOOLEAN_ASSIGMENT_LIST = + protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); - protected static final Dependency FEATURE_MODEL = - Dependency.newDependency(IFeatureModel.class); + protected static final Dependency FEATURE_MODEL = Dependency.newDependency(IFeatureModel.class); + protected static final Dependency ANALYSIS = Dependency.newDependency(Boolean.class); public ComputeUniformity(IComputation featureModel) { - super(Computations.of(new BooleanAssignmentList(new VariableMap())), featureModel); + super( + Computations.of(new BooleanAssignmentList(new VariableMap())), + featureModel, + Computations.of(Boolean.TRUE)); } @Override - public Result> compute(List dependencyList, Progress progress) { - BooleanAssignmentList booleanAssignmentList = BOOLEAN_ASSIGMENT_LIST.get(dependencyList); + public Result> compute(List dependencyList, Progress progress) { + BooleanAssignmentList booleanAssignmentList = BOOLEAN_ASSIGNMENT_LIST.get(dependencyList); IFeatureModel featureModel = FEATURE_MODEL.get(dependencyList); - IComputation iFormula = Computations.of(featureModel).map(ComputeFormula::new); - IFormula fmFormula = iFormula.compute(); - //new VariableMap(fmFormula); - System.out.println(fmFormula.print()); - IComputation solutionCountComputation = Computations.of(fmFormula).map(ComputeNNFFormula::new) - .map(ComputeCNFFormula::new).map(ComputeSolutionCount::new); - BigInteger solutionsCount = solutionCountComputation.compute(); - HashMap returnMap = new HashMap(); - HashMap fmMap = new HashMap(); - HashMap sampleMap = new HashMap(); + IFormula fmFormula = + Computations.of(featureModel).map(ComputeFormula::new).compute(); + float solutionsCount = Computations.of(fmFormula) + .map(ComputeNNFFormula::new) + .map(ComputeCNFFormula::new) + .map(ComputeSolutionCount::new) + .compute() + .floatValue(); + LinkedHashMap returnedMap = new LinkedHashMap(); VariableMap fmVariableMap = new VariableMap(fmFormula); - + String featureModelPrefix = "_FeatureModel"; + String assignmentsSamplePrefix = "_AssignmentsSample"; + for (String varName : fmVariableMap.getVariableNames()) { - fmMap.put(varName, (float) 0); - sampleMap.put(varName, (float) 0); + returnedMap.put(varName + featureModelPrefix, (float) 0); + returnedMap.put(varName + assignmentsSamplePrefix, (float) 0); } - - fmMap.put("all", solutionsCount.floatValue()); - + + // Calculate the number of valid configurations per feature in the full featureModel. for (String varName : fmVariableMap.getVariableNames()) { - Reference currentFormula = new Reference(new And((IFormula)fmFormula.getChildren().get(0), new Literal(varName))); - currentFormula.setFreeVariables(((Reference)fmFormula).getFreeVariables()); - IFormula NNFFormula = Computations.of((IFormula)currentFormula).map(ComputeNNFFormula::new).compute(); - fmMap.put(varName, Computations.of(NNFFormula) - .map(ComputeCNFFormula::new).map(ComputeSolutionCount::new).compute().floatValue()); + Reference currentFormula = + new Reference(new And((IFormula) fmFormula.getChildren().get(0), new Literal(varName))); + currentFormula.setFreeVariables(((Reference) fmFormula).getFreeVariables()); + IFormula NNFFormula = Computations.of((IFormula) currentFormula) + .map(ComputeNNFFormula::new) + .compute(); + returnedMap.replace( + varName + featureModelPrefix, + Computations.of(NNFFormula) + .map(ComputeCNFFormula::new) + .map(ComputeSolutionCount::new) + .compute() + .floatValue()); } - + + // Calculate the number of valid configurations per feature in the full assignmentSample. int assignmentSolutionsCount = 0; - for(BooleanAssignment booleanAssignment : booleanAssignmentList.getAll()) { - IFormula currentIFormulaAssignment = new And(); - List currentAssignmentVariables = new LinkedList(); - for (int index : booleanAssignment.get()) { - if(fmVariableMap.get(index).isPresent()) { - currentIFormulaAssignment = new And(currentIFormulaAssignment, new Literal(fmVariableMap.get(index).orElseThrow())); - currentAssignmentVariables.add(fmVariableMap.get(index).get()); - } - } - Reference currentFormula = new Reference(new And((IFormula)fmFormula.getChildren().get(0), currentIFormulaAssignment)); - System.out.println(fmFormula.print()); - System.out.println("Assignment: " + booleanAssignment + "\n" + currentFormula.print()); - System.out.println(Computations.of((IFormula)currentFormula).map(ComputeNNFFormula::new) - .map(ComputeCNFFormula::new).map(ComputeSolutionCount::new).compute()); - currentFormula.setFreeVariables(((Reference)fmFormula).getFreeVariables()); - - if(Computations.of((IFormula)currentFormula).map(ComputeNNFFormula::new) - .map(ComputeCNFFormula::new).map(ComputeSolutionCount::new).compute().intValue() > 0) { - assignmentSolutionsCount++; - for (String key : currentAssignmentVariables) { - sampleMap.replace(key, sampleMap.get(key) + 1); - } - } + for (BooleanAssignment booleanAssignment : booleanAssignmentList.getAll()) { + LinkedList allLiterals = new LinkedList(); + List currentAssignmentVariables = new LinkedList(); + for (int index : booleanAssignment.get()) { + if (fmVariableMap.get(index).isPresent()) { + allLiterals.add(new Literal(fmVariableMap.get(index).get())); + currentAssignmentVariables.add(fmVariableMap.get(index).get()); + } else if (fmVariableMap.get(Math.abs(index)).isPresent()) { + allLiterals.add(new Not( + new Literal(fmVariableMap.get(Math.abs(index)).get()))); + } else { + Result.empty(); + } + } + IFormula currentIFormulaAssignment = new And(allLiterals); + Reference currentFormula = + new Reference(new And((IFormula) fmFormula.getChildren().get(0), currentIFormulaAssignment)); + currentFormula.setFreeVariables(((Reference) fmFormula).getFreeVariables()); + if (Computations.of((IFormula) currentFormula) + .map(ComputeNNFFormula::new) + .map(ComputeCNFFormula::new) + .map(ComputeSatisfiability::new) + .compute()) { + assignmentSolutionsCount++; + for (String key : currentAssignmentVariables) { + returnedMap.replace( + key + assignmentsSamplePrefix, returnedMap.get(key + assignmentsSamplePrefix) + 1); + } + } + } + + if (ANALYSIS.get(dependencyList)) { + for (String varName : fmVariableMap.getVariableNames()) { + float sampleShare = returnedMap.get(varName + assignmentsSamplePrefix) / assignmentSolutionsCount; + float featureShare = returnedMap.get(varName + featureModelPrefix) / solutionsCount; + returnedMap.remove(varName + assignmentsSamplePrefix); + returnedMap.remove(varName + featureModelPrefix); + returnedMap.put(varName, sampleShare - featureShare); + } + } else { + returnedMap.put("FeatureModel Valid", solutionsCount); + returnedMap.put("AssignmentsSample Valid", (float) assignmentSolutionsCount); } - - System.out.println("sampleMap: \n" + sampleMap); - System.out.println("assignmentSolutionsCount: " + assignmentSolutionsCount); - System.out.println("solutionsCount: " + solutionsCount); - return Result.of(fmMap); + return Result.of(returnedMap); } -} \ No newline at end of file +} diff --git a/src/main/java/de/featjar/feature/model/transformer/ComputeFormula.java b/src/main/java/de/featjar/feature/model/transformer/ComputeFormula.java index c1c017ae..092e68f5 100644 --- a/src/main/java/de/featjar/feature/model/transformer/ComputeFormula.java +++ b/src/main/java/de/featjar/feature/model/transformer/ComputeFormula.java @@ -85,11 +85,11 @@ public Result compute(List dependencyList, Progress progress) } handleGroups(constraints, featureLiteral, node); }); - //for loop constraints + // for loop constraints for (IConstraint constraint : featureModel.getConstraints()) { - constraints.add(constraint.getFormula()); + constraints.add(constraint.getFormula()); } - + Reference reference = new Reference(new And(constraints)); reference.setFreeVariables(variables); return Result.of(reference); diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index 993dafe7..4a0242e4 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -1,22 +1,22 @@ /* * Copyright (C) 2025 FeatJAR-Development-Team * - * This file is part of FeatJAR-formula. + * This file is part of FeatJAR-feature-model. * - * formula is free software: you can redistribute it and/or modify it + * feature-model is free software: you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by * the Free Software Foundation, either version 3.0 of the License, * or (at your option) any later version. * - * formula is distributed in the hope that it will be useful, + * feature-model is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. * See the GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License - * along with formula. If not, see . + * along with feature-model. If not, see . * - * See for further information. + * See for further information. */ package de.featjar.feature.model.analysis; @@ -25,25 +25,21 @@ import de.featjar.base.FeatJAR; import de.featjar.base.computation.Computations; import de.featjar.base.computation.IComputation; -import de.featjar.base.data.Result; import de.featjar.base.data.identifier.Identifiers; -import de.featjar.base.io.IO; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; -import de.featjar.feature.model.analysis.ComputeDistributionFeatureSelections; -import de.featjar.feature.model.analysis.ComputeFeatureCounter; -import de.featjar.feature.model.analysis.ComputeNumberConfigurations; -import de.featjar.feature.model.analysis.ComputeNumberVariables; -import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.feature.model.transformer.ComputeFormula; import de.featjar.formula.VariableMap; import de.featjar.formula.assignment.BooleanAssignment; import de.featjar.formula.assignment.BooleanAssignmentList; - -import java.nio.file.Files; -import java.nio.file.Paths; +import de.featjar.formula.structure.IFormula; +import de.featjar.formula.structure.connective.Implies; +import de.featjar.formula.structure.connective.Or; +import de.featjar.formula.structure.predicate.Literal; import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.LinkedList; import org.junit.jupiter.api.Test; @@ -69,23 +65,57 @@ public BooleanAssignmentList createAssignmentList() { new BooleanAssignment()); return booleanAssignmentList; } - - public BooleanAssignmentList createAssignmentListUniformity() { - LinkedList variableNames = new LinkedList(); - variableNames.add("Root"); - variableNames.add("A"); - variableNames.add("B"); - VariableMap variableMap = new VariableMap(variableNames); + public BooleanAssignmentList createAssignmentListUniformity(FeatureModel featureModel) { + LinkedList variableNames = new LinkedList(); + IComputation iFormula = + Computations.of((IFeatureModel) featureModel).map(ComputeFormula::new); + IFormula fmFormula = iFormula.compute(); + VariableMap variableMap = new VariableMap(fmFormula); BooleanAssignmentList booleanAssignmentList = new BooleanAssignmentList( variableMap, - new BooleanAssignment(2), - new BooleanAssignment(3), - new BooleanAssignment(-2), - new BooleanAssignment(-3)); + new BooleanAssignment( + variableMap.get("ConfigDB").get(), + variableMap.get("Get").get(), + variableMap.get("Windows").get(), + -variableMap.get("Put").get(), + -variableMap.get("Delete").get(), + -variableMap.get("Transactions").get(), + -variableMap.get("Linux").get()), + new BooleanAssignment( + variableMap.get("ConfigDB").get(), + variableMap.get("Get").get(), + -variableMap.get("Windows").get(), + variableMap.get("Put").get(), + variableMap.get("Delete").get(), + variableMap.get("Transactions").get(), + variableMap.get("Linux").get()), + new BooleanAssignment( + variableMap.get("ConfigDB").get(), + variableMap.get("Get").get(), + -variableMap.get("Windows").get(), + -variableMap.get("Put").get(), + -variableMap.get("Delete").get(), + -variableMap.get("Transactions").get(), + -variableMap.get("Linux").get()), + new BooleanAssignment( + variableMap.get("ConfigDB").get(), + variableMap.get("Get").get(), + -variableMap.get("Windows").get(), + -variableMap.get("Put").get(), + -variableMap.get("Delete").get(), + variableMap.get("Transactions").get(), + variableMap.get("Linux").get())); return booleanAssignmentList; } + public FeatureModel createMediumFeatureModel() { + FeatureModel fm = new FeatureModel(); + fm.addFeatureTreeRoot(generateMediumTree()); + fm.addConstraint(new Implies(new Literal("Transactions"), new Or(new Literal("Put"), new Literal("Delete")))); + return fm; + } + @Test public void computeDistributionFeaturesSelectionsTest() { BooleanAssignmentList booleanAssignmentList = createAssignmentList(); @@ -95,38 +125,39 @@ public void computeDistributionFeaturesSelectionsTest() { assertEquals(7, selectionDistribution.get("selected")); assertEquals(6, selectionDistribution.get("deselected")); assertEquals(22, selectionDistribution.get("undefined")); + System.out.println("Distribution of feature selection: \n" + selectionDistribution); } @Test public void computeFeatureCounterTest() { BooleanAssignmentList booleanAssignmentList = createAssignmentList(); - IComputation>> computational = - Computations.of(booleanAssignmentList).map(ComputeFeatureCounter::new); - HashMap> featureCounter = computational.compute(); - System.out.println(featureCounter); - assertEquals(2, featureCounter.get("A").get("selected")); - assertEquals(1, featureCounter.get("B").get("selected")); - assertEquals(0, featureCounter.get("C").get("selected")); - assertEquals(1, featureCounter.get("D").get("selected")); - assertEquals(2, featureCounter.get("E").get("selected")); - assertEquals(1, featureCounter.get("F").get("selected")); - assertEquals(0, featureCounter.get("G").get("selected")); - - assertEquals(1, featureCounter.get("A").get("deselected")); - assertEquals(1, featureCounter.get("B").get("deselected")); - assertEquals(1, featureCounter.get("C").get("deselected")); - assertEquals(0, featureCounter.get("D").get("deselected")); - assertEquals(1, featureCounter.get("E").get("deselected")); - assertEquals(2, featureCounter.get("F").get("deselected")); - assertEquals(0, featureCounter.get("G").get("deselected")); - - assertEquals(2, featureCounter.get("A").get("undefined")); - assertEquals(3, featureCounter.get("B").get("undefined")); - assertEquals(4, featureCounter.get("C").get("undefined")); - assertEquals(4, featureCounter.get("D").get("undefined")); - assertEquals(2, featureCounter.get("E").get("undefined")); - assertEquals(2, featureCounter.get("F").get("undefined")); - assertEquals(5, featureCounter.get("G").get("undefined")); + HashMap featureCounter = Computations.of(booleanAssignmentList) + .map(ComputeFeatureCounter::new) + .compute(); + assertEquals(2, featureCounter.get("A_selected")); + assertEquals(1, featureCounter.get("B_selected")); + assertEquals(0, featureCounter.get("C_selected")); + assertEquals(1, featureCounter.get("D_selected")); + assertEquals(2, featureCounter.get("E_selected")); + assertEquals(1, featureCounter.get("F_selected")); + assertEquals(0, featureCounter.get("G_selected")); + + assertEquals(1, featureCounter.get("A_deselected")); + assertEquals(1, featureCounter.get("B_deselected")); + assertEquals(1, featureCounter.get("C_deselected")); + assertEquals(0, featureCounter.get("D_deselected")); + assertEquals(1, featureCounter.get("E_deselected")); + assertEquals(2, featureCounter.get("F_deselected")); + assertEquals(0, featureCounter.get("G_deselected")); + + assertEquals(2, featureCounter.get("A_undefined")); + assertEquals(3, featureCounter.get("B_undefined")); + assertEquals(4, featureCounter.get("C_undefined")); + assertEquals(4, featureCounter.get("D_undefined")); + assertEquals(2, featureCounter.get("E_undefined")); + assertEquals(2, featureCounter.get("F_undefined")); + assertEquals(5, featureCounter.get("G_undefined")); + System.out.println("Distribution of feature selection per feature: \n" + featureCounter); } @Test @@ -135,6 +166,7 @@ public void computeNumberConfigurationTest() { IComputation computational = Computations.of(booleanAssignmentList).map(ComputeNumberConfigurations::new); assertEquals(5, computational.compute()); + System.out.println("Number of configurations: \n" + computational.compute()); } @Test @@ -143,58 +175,91 @@ public void computeNumberVariablesTest() { IComputation computational = Computations.of(booleanAssignmentList).map(ComputeNumberVariables::new); assertEquals(7, computational.compute()); + System.out.println("Number of variables: \n" + computational.compute()); } - + @Test public void computeUniformity() { - FeatJAR.initialize(); - /* - if (Files.exists(Paths.get("../formula/src/testFixtures/resources/Automotive02_V1"))) { - System.out.println("The file exists."); - } else { - System.out.println("The file does not exist."); - } - - Result featureModelFormatResult = Result.empty(); - featureModelFormatResult.of(IO.load(Paths.get("../formula/src/testFixtures/resources/Automotive02_V1") - , FeatureModelFormats.getInstance()).get()); - System.out.println(featureModelFormatResult.get().getNumberOfConstraints());*/ - IComputation> computation = Computations.of(IO.load(Paths.get("../formula/src/testFixtures/resources/testFeatureModels/basic.xml"), - FeatureModelFormats.getInstance()).get()).map(ComputeUniformity::new).set(ComputeUniformity.BOOLEAN_ASSIGMENT_LIST, createAssignmentListUniformity()); - - //IComputation> computation = Computations.of(()).map(ComputeUniformity::new); - HashMap result = computation.compute(); - System.out.println(result); + FeatJAR.initialize(); + IComputation> computation = Computations.of( + (IFeatureModel) createMediumFeatureModel()) + .map(ComputeUniformity::new) + .set( + ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, + createAssignmentListUniformity(createMediumFeatureModel())) + .set(ComputeUniformity.ANALYSIS, false); + + HashMap result = computation.compute(); + assertEquals(26, result.get("FeatureModel Valid")); + assertEquals(2, result.get("AssignmentsSample Valid")); + assertEquals(26, result.get("ConfigDB_FeatureModel")); + assertEquals(2, result.get("ConfigDB_AssignmentsSample")); + assertEquals(26, result.get("API_FeatureModel")); + assertEquals(0, result.get("API_AssignmentsSample")); + assertEquals(26, result.get("OS_FeatureModel")); + assertEquals(0, result.get("OS_AssignmentsSample")); + assertEquals(14, result.get("Get_FeatureModel")); + assertEquals(2, result.get("Get_AssignmentsSample")); + assertEquals(16, result.get("Put_FeatureModel")); + assertEquals(1, result.get("Put_AssignmentsSample")); + assertEquals(16, result.get("Delete_FeatureModel")); + assertEquals(1, result.get("Delete_AssignmentsSample")); + assertEquals(13, result.get("Windows_FeatureModel")); + assertEquals(1, result.get("Windows_AssignmentsSample")); + assertEquals(13, result.get("Linux_FeatureModel")); + assertEquals(1, result.get("Linux_AssignmentsSample")); + assertEquals(1, result.get("Windows_AssignmentsSample")); + assertEquals(13, result.get("Linux_FeatureModel")); + assertEquals(1, result.get("Linux_AssignmentsSample")); + assertEquals(12, result.get("Transactions_FeatureModel")); + assertEquals(1, result.get("Transactions_AssignmentsSample")); + assertEquals(26, result.get("FeatureModel Valid")); + assertEquals(2, result.get("AssignmentsSample Valid")); + System.out.println("Descriptive validtiy map: \n" + result); + + computation.set(ComputeUniformity.ANALYSIS, true); + result = computation.compute(); + assertEquals(0, result.get("ConfigDB")); + assertEquals(-1, result.get("API")); + assertEquals(-1, result.get("OS")); + assertEquals(((float) 2 / 2) - ((float) 14 / 26), result.get("Get")); + assertEquals(((float) 1 / 2) - ((float) 16 / 26), result.get("Put")); + assertEquals(((float) 1 / 2) - ((float) 16 / 26), result.get("Delete")); + assertEquals(0, result.get("Windows")); + assertEquals(0, result.get("Linux")); + assertEquals(((float) 1 / 2) - ((float) 12 / 26), result.get("Transactions")); + System.out.println("Commonality difference per features: \n" + result); } - + private IFeatureTree generateMediumTree() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); IFeatureTree treeRoot = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); IFeature featureAPI = featureModel.mutate().addFeature("API"); - IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); - treeAPI.isMandatory(); IFeature featureGet = featureModel.mutate().addFeature("Get"); - treeAPI.mutate().addFeatureBelow(featureGet); IFeature featurePut = featureModel.mutate().addFeature("Put"); - treeAPI.mutate().addFeatureBelow(featurePut); IFeature featureDelete = featureModel.mutate().addFeature("Delete"); - treeAPI.mutate().addFeatureBelow(featureDelete); - treeAPI.mutate().toOrGroup(); IFeature featureOS = featureModel.mutate().addFeature("OS"); - IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); - treeOS.isMandatory(); IFeature featureWindows = featureModel.mutate().addFeature("Windows"); - treeOS.mutate().addFeatureBelow(featureWindows); + + IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); + IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); IFeature featureLinux = featureModel.mutate().addFeature("Linux"); + + treeAPI.mutate().addFeatureBelow(featureGet); + treeAPI.mutate().addFeatureBelow(featurePut); + treeAPI.mutate().addFeatureBelow(featureDelete); + treeOS.mutate().addFeatureBelow(featureWindows); treeOS.mutate().addFeatureBelow(featureLinux); + + treeAPI.mutate().toOrGroup(); treeOS.mutate().toAlternativeGroup(); - IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); - IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); - treeTransactions.isOptional(); + treeRoot.mutate().makeMandatory(); + treeAPI.mutate().makeMandatory(); + treeOS.mutate().makeMandatory(); return treeRoot; } From ad83fc4bed2d742e0010fcb8c24409684ca05933 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 15:33:00 +0200 Subject: [PATCH 186/257] test: wrote a first round of tests --- .../model/analysis/visualization/Testtmp.java | 4 +- .../VisualizeFeatureModelStatsTest.java | 136 ++++++++++++++++++ .../feature/model}/visualization/model.xml | 0 3 files changed, 138 insertions(+), 2 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java rename src/{main/java/de/featjar/feature/model/analysis => test/java/de/featjar/feature/model}/visualization/model.xml (100%) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java index 38f85c50..81617099 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java @@ -97,7 +97,7 @@ public static void main(String[] args) throws Exception { ); AnalysisTree mediumAnalysisTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); - Path path = Paths.get("src/main/java/de/featjar/feature/model/analysis/visualization/model.xml"); + Path path = Paths.get("src/test/java/de/featjar/feature/model/visualization/model.xml"); Result load = IO.load(path, new XMLFeatureModelFormat()); FeatureModel model = (FeatureModel) load.orElseThrow(); map = printStatistics.collectStats( @@ -113,7 +113,7 @@ public static void main(String[] args) throws Exception { //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(generateEmptyTree()); //viz.displayChart(); - viz2.exportChartToPDF(99, "export.pdf"); + viz2.displayChart(); } } diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java new file mode 100644 index 00000000..c999cb14 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -0,0 +1,136 @@ +package de.featjar.feature.model.visualization; +import de.featjar.base.data.Result; +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.base.io.IO; +import de.featjar.feature.model.FeatureModel; +import de.featjar.feature.model.IFeature; +import de.featjar.feature.model.IFeatureModel; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.visualization.VisualizeGroupDistribution; +import de.featjar.feature.model.cli.PrintStatistics; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; +import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; +import org.junit.jupiter.api.Test; + +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.LinkedHashMap; + +import static org.junit.jupiter.api.Assertions.*; + +public class VisualizeFeatureModelStatsTest { + AnalysisTree bigTree = getBigAnalysisTree(); + AnalysisTree mediumTree = getMediumAnalysisTree(); + String defaultExportName = "src/test/java/de/featjar/feature/model/visualization/model.xml"; + + /** + * Helper function. + * Yields feature model with a single tree. This feature tree has three nodes under the root: + * API is mandatory and below it is an or-group with the features Get, Put, Delete. + * OS is also mandatory and below it is an alternative group with the features Windows, Linux. + * Transactions is an optional feature below the root. + * @return a medium-sized feature model for testing purposes. + */ + public FeatureModel buildMediumFeatureModel() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree treeRoot = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); + + IFeature featureAPI = featureModel.mutate().addFeature("API"); + IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); + treeAPI.isMandatory(); + IFeature featureGet = featureModel.mutate().addFeature("Get"); + treeAPI.mutate().addFeatureBelow(featureGet); + IFeature featurePut = featureModel.mutate().addFeature("Put"); + treeAPI.mutate().addFeatureBelow(featurePut); + IFeature featureDelete = featureModel.mutate().addFeature("Delete"); + treeAPI.mutate().addFeatureBelow(featureDelete); + treeAPI.mutate().toOrGroup(); + + IFeature featureOS = featureModel.mutate().addFeature("OS"); + IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); + treeOS.isMandatory(); + IFeature featureWindows = featureModel.mutate().addFeature("Windows"); + treeOS.mutate().addFeatureBelow(featureWindows); + IFeature featureLinux = featureModel.mutate().addFeature("Linux"); + treeOS.mutate().addFeatureBelow(featureLinux); + treeOS.mutate().toAlternativeGroup(); + + IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); + IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); + treeTransactions.isOptional(); + + return featureModel; + } + + /** + * Helper function. Converts a feature model into an {@link AnalysisTree} + */ + public AnalysisTree analysisTreeFromFeatureModel(FeatureModel featureModel) { + PrintStatistics printStatistics = new PrintStatistics(); + LinkedHashMap map = printStatistics.collectStats( + featureModel, + PrintStatistics.AnalysesScope.ALL + ); + return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + } + + public AnalysisTree analysisTreeFromXML (Path path) { + Result load = IO.load(path, new XMLFeatureModelFormat()); + FeatureModel model = (FeatureModel) load.orElseThrow(); + + PrintStatistics printStatistics = new PrintStatistics(); + LinkedHashMap map = printStatistics.collectStats( + model, + PrintStatistics.AnalysesScope.ALL + ); + return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + } + + public AnalysisTree getBigAnalysisTree () { + return analysisTreeFromXML(Paths.get("src/test/java/de/featjar/feature/model/visualization/model.xml")); + } + + public AnalysisTree getMediumAnalysisTree() { + return analysisTreeFromFeatureModel(buildMediumFeatureModel()); + } + + // todo auch Tests für die andere stats art mit constraints bla + + @Test + void regularLivePreview() { + VisualizeGroupDistribution viz; + + viz = new VisualizeGroupDistribution(mediumTree); + viz.displayChart(); + + viz = new VisualizeGroupDistribution(bigTree); + viz.displayChart(); + + // todo have to think about how to test this + assertTrue(true); + } + + @Test + void pdfValidIndex() { + VisualizeGroupDistribution viz; + + viz = new VisualizeGroupDistribution(mediumTree); + assertEquals(0, viz.exportChartToPDF(0, defaultExportName)); + + viz = new VisualizeGroupDistribution(bigTree); + assertEquals(0, viz.exportChartToPDF(0, defaultExportName)); + } + + @Test + void pdfInvalidIndex() { + VisualizeGroupDistribution viz; + + viz = new VisualizeGroupDistribution(mediumTree); + assertEquals(1, viz.exportChartToPDF(99, defaultExportName)); + + viz = new VisualizeGroupDistribution(bigTree); + assertEquals(1, viz.exportChartToPDF(99, defaultExportName)); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/model.xml b/src/test/java/de/featjar/feature/model/visualization/model.xml similarity index 100% rename from src/main/java/de/featjar/feature/model/analysis/visualization/model.xml rename to src/test/java/de/featjar/feature/model/visualization/model.xml From 4b74b07be18fb5e6339b0c1885aab340d7839e69 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 15:39:51 +0200 Subject: [PATCH 187/257] refactor: slightly smarter reuse of polymorphs --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 5bb68202..838fb189 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -294,7 +294,7 @@ public int exportChartToPDF(Integer index, String path) { */ public int exportChartToPDF(String path) { if (chartsAreEmptyDisplay()) {return 2;} - return exportChartToPDF(charts.get(0), path); + return exportChartToPDF(0, path); } /** From 6ab001e8ec075126b7246f504246d759b295647f Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 15:40:04 +0200 Subject: [PATCH 188/257] doc: small reminder to test with other feature models --- .../model/visualization/VisualizeFeatureModelStatsTest.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index c999cb14..b221530d 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -97,6 +97,7 @@ public AnalysisTree getMediumAnalysisTree() { } // todo auch Tests für die andere stats art mit constraints bla + // todo Tests für FeatureModels mit mehreren Bäumen -> need custom feature model @Test void regularLivePreview() { From 5600073a014b5ce9b78f6b130b72494b0819b9ff Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 15:59:33 +0200 Subject: [PATCH 189/257] test: added tests for live preview --- .../AVisualizeFeatureModelStats.java | 46 +++++++++++++++---- .../VisualizeFeatureModelStatsTest.java | 10 ++-- 2 files changed, 41 insertions(+), 15 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 838fb189..e66a615b 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -3,6 +3,7 @@ import de.featjar.base.FeatJAR; import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; +import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D; @@ -15,6 +16,7 @@ import org.knowm.xchart.SwingWrapper; import org.knowm.xchart.internal.chartpart.Chart; +import javax.swing.*; import java.awt.geom.AffineTransform; import java.io.IOException; import java.util.*; @@ -190,41 +192,65 @@ private HashMap extractAnalysisMap() { /** * Creates a live preview pop-up window of a chart. + * @param chart the chart that will be displayed + * @return 0 on success, 1 on general error */ - public void displayChart (Chart chart) { - if (chartsAreEmptyDisplay()) {return;} - new SwingWrapper<>(chart).displayChart(); + public int displayChart (Chart chart) { + try { + JFrame jframe = new SwingWrapper<>(chart).displayChart(); + if (jframe == null) { + FeatJAR.log().error("Could not display chart! Received null object from XChart SwingWrapper."); + return 1; + } + } catch (Exception e) { + FeatJAR.log().error("Could not display chart!" + e); + return 1; + } + return 0; } /** * Creates a live preview pop-up window of the FIRST internally generated chart. * This chart usually corresponds to the first feature tree in the feature model. + * @return 0 on success, 1 on general error, 2 on empty internal chart list */ - public void displayChart() { - if (chartsAreEmptyDisplay()) {return;} - this.displayChart(0); + public int displayChart() { + if (chartsAreEmptyDisplay()) {return 2;} + return this.displayChart(0); } /** * Creates a live preview pop-up window of an internally generated chart, fetched by index. + * @return 0 on success, 1 on general error, 2 on empty internal chart list */ - public void displayChart (Integer index) { + public int displayChart (Integer index) { + if (chartsAreEmptyDisplay()) {return 2;} try { this.displayChart(this.charts.get(index)); } catch (IndexOutOfBoundsException e) { FeatJAR.log().error("Unable to fetch chart with index "+ index + ": " + e); + return 1; } + return 0; } /** * Creates live preview pop-up windows of ALL internally generated charts. + * @return 0 on success, 1 on general error, 2 on empty internal chart list */ - public void displayAllCharts() { - if (chartsAreEmptyDisplay()) {return;} + public int displayAllCharts() { + if (chartsAreEmptyDisplay()) {return 2;} + + int returnValue = 0; for (Chart chart : this.charts) { - this.displayChart(chart); + returnValue = this.displayChart(chart); + if (returnValue != 0) { + break; + } } + + return returnValue; } /** diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index b221530d..bdaa5f7e 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -99,18 +99,18 @@ public AnalysisTree getMediumAnalysisTree() { // todo auch Tests für die andere stats art mit constraints bla // todo Tests für FeatureModels mit mehreren Bäumen -> need custom feature model + /** + * This Test may have to be disabled or restructured if it creates confusing momentary popups on every gradle build + */ @Test void regularLivePreview() { VisualizeGroupDistribution viz; viz = new VisualizeGroupDistribution(mediumTree); - viz.displayChart(); + assertEquals(0, viz.displayChart()); viz = new VisualizeGroupDistribution(bigTree); - viz.displayChart(); - - // todo have to think about how to test this - assertTrue(true); + assertEquals(0, viz.displayChart()); } @Test From 1d60785fb849355497631bf7b3403ae6f60c8782 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Wed, 15 Oct 2025 16:00:52 +0200 Subject: [PATCH 190/257] test: added tests, refactored main file and improved docs --- .../cli/ConfigurationFormatConversion.java | 433 ++++-------------- .../ConfigurationFormatConversionTest.java | 406 ++++++++++++++++ 2 files changed, 488 insertions(+), 351 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 8b4cac07..39eeb521 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -24,31 +24,13 @@ import de.featjar.base.cli.ICommand; import de.featjar.base.cli.Option; import de.featjar.base.cli.OptionList; -import de.featjar.base.data.Result; import de.featjar.base.io.IO; import de.featjar.base.io.format.IFormat; -import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.IFeatureModel; -import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; -import de.featjar.feature.model.io.FeatureModelFormats; import de.featjar.formula.assignment.BooleanAssignmentList; import de.featjar.formula.io.BooleanAssignmentListFormats; -import de.featjar.formula.io.IBooleanAssignmentListFormat; -import de.featjar.formula.io.binary.BooleanAssignmentListBinaryFormat; -import de.featjar.formula.io.csv.BooleanAssignmentListCSVFormat; -import de.featjar.formula.io.dimacs.BooleanAssignmentListDimacsFormat; -import de.featjar.formula.io.textual.BooleanAssignmentListSimpleTextFormat; -import de.featjar.formula.io.textual.BooleanAssignmentListTextFormat; - -import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.EnumMap; -import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.stream.Collectors; @@ -58,40 +40,47 @@ * * @author Knut & Kilian */ - -// BooleanAssignmentValueMapFormat implements IFormat - public class ConfigurationFormatConversion implements ICommand { - + public enum TypeTXT { - SIMPLETXT, - TXT, + SIMPLE_TXT("simple text"), + DEFAULT_TXT("text"); + + public final String description; + + TypeTXT(String description) { + this.description = description; + } + + @Override + public String toString() { + return description; + } } private static final List supportedInputFileExtensions = - BooleanAssignmentListFormats.getInstance().getExtensions().stream() + BooleanAssignmentListFormats.getInstance().getExtensions().stream() .filter(IFormat::supportsParse) .map(IFormat::getFileExtension) .collect(Collectors.toList()); private static final List supportedOutputFileExtensions = - BooleanAssignmentListFormats.getInstance().getExtensions().stream() + BooleanAssignmentListFormats.getInstance().getExtensions().stream() .filter(IFormat::supportsWrite) .map(IFormat::getFileExtension) .collect(Collectors.toList()); public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) - .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions) - .setValidator(Option.PathValidator); + .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions); public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) .setDescription("Path to output file. Accepted File Types: " + supportedOutputFileExtensions); public static final Option OVERWRITE = - Option.newFlag("overwrite").setDescription("Overwrite output file."); - - public static final Option TypeTXT = - Option.newEnumOption("typetxt", TypeTXT.class).setDescription("Necessary if output is desired to be .list"); + Option.newFlag("overwrite").setDescription("Overwrite existing file at output path."); + + public static final Option TYPE_TXT = Option.newEnumOption("typeTXT", TypeTXT.class) + .setDescription("Specification necessary if output is desired to be .txt"); /** * @return all options registered for the calling class. @@ -101,242 +90,53 @@ public final List> getOptions() { } /** - * For info loss map; indicates whether a feature is supported fully, partially, or not at all. - */ - private enum SupportLevel { - NO(0), - YES(1); - - public final int rank; - - SupportLevel(int rank) { - this.rank = rank; - } - - boolean isLessThan(SupportLevel other) { - return this.rank < other.rank; - } - } - - /** - * For info loss map. - * Saving name as well as a description in case we need to explain it to the user later. - */ - private enum FileInfo { - basicHierarchy("General hierarchical Structure"), - subgroupHierarchy("Hierarchy with subgroups"), - featureDescription("Features with descriptions"), - featureAttributes("Features with attributes"), - featureCardinality("Cardinality of features"), - booleanOperators("Features of boolean operators"), - allOperators("Features of all operators"), - parseable("File can be used for input"); - - public final String name; - - FileInfo(String name) { - this.name = name; - } - - @Override - public String toString() { - return name; - } - } - - /** - * main function for handling format conversion + * main function for handling conversion of BooleanAssignmentList files. * @param optionParser supplied by command line execution. * * @return 0 on success - * 1 if output/input aren't present - * 2 if input/output file type is invalid - * 3 if the model could not be parsed, - * 4 if a file is already present at output path and no overwrite is specified - * 5 on IOException + * 1 on invalid input or output path + * 2 on unsupported input or output file extension + * 3 on failure to save BooleanAssignmentList because file already exists on path directory and --overwrite flag is not used */ @Override public int run(OptionList optionParser) { + // validating input/output + String intputFileExtension = + IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get().toString()); + String outputFileExtension = + IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get().toString()); -// String outputFileExtension; -// -// if (optionParser.getResult(TypeTXT).isPresent()) { -// outputFileExtension = optionParser.getResult(TypeTXT).get().toString(); -// } else { -// outputFileExtension = -// IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); -// } - - String outputFileExtension = IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get().toString()); - - if(outputFileExtension != "list" && optionParser.getResult(TypeTXT).isPresent()){ - FeatJAR.log().warning("Conflicting CLI options: " + outputFileExtension + " format in Path and TXT format due to --typetxt.\n Continuing to use TXT format..."); - } - - if(!checkIfInputOutputIsPresent(optionParser)) { - return 1; - }; - - - BooleanAssignmentList model = IO.load(optionParser.getResult(INPUT_OPTION).orElseThrow(), BooleanAssignmentListFormats.getInstance()).get(); - - saveFile(optionParser.getResult(OUTPUT_OPTION).orElseThrow(), model, outputFileExtension, optionParser.get(OVERWRITE)); - -// -// -// - - -// -// if (!checkIfInputOutputIsPresent(optionParser)) { -// return 1; -// } -// Path outputPath = optionParser.getResult(OUTPUT_OPTION).orElseThrow(); -// -// // check if provided file extensions are supported -// String inputFileExtension = -// IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get()); -// String outputFileExtension = -// IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get()); -// if (!checkIfFileExtensionsValid(inputFileExtension, outputFileExtension)) { -// return 2; -// } -// -// System.out.println(Files.exists(optionParser.getResult(INPUT_OPTION).orElseThrow())); -// -// // informing user about information loss during conversion between file formats -// //infoLossMessage(inputFileExtension, outputFileExtension); -// -// // check if model was corrected extracted from input -// Result load = inputParser(optionParser); -// if (load == null) { -// FeatJAR.log().error("No model parsed from input file!"); -// return 3; -// } -//// try { -//// IO.save(load.get(), optionParser.getResult(OUTPUT_OPTION).orElseThrow(), new BooleanAssignmentListCSVFormat()); -//// -//// } catch (Exception e) { -//// FeatJAR.log().error(e); -//// } - - return 0 /*saveFile(outputPath, model, outputFileExtension, optionParser.get(OVERWRITE))*/; - } - - /** - * Informs user about potential information loss occurring during file conversion - * @param inputFileExtension file extension of the input file (lower case, no leading dot) - * @param outputFileExtension file extension of the output file (lower case, no leading dot) - */ - private void infoLossMessage(String inputFileExtension, String outputFileExtension) { - - StringBuilder msg = new StringBuilder(); - msg.append("Info Loss:\n"); - - Map> infoLossMap = buildInfoLossMap(); - - Map iSupports = infoLossMap.get(inputFileExtension); - Map oSupports = infoLossMap.get(outputFileExtension); - - if (iSupports == null || oSupports == null) { - return; + if (!checkIfInputOutputIsPresent(optionParser)) { + System.out.println("HEEEEEEEEEEEERE"); + return 1; } - boolean infoLossPresent = false; - for (FileInfo fileInfo : iSupports.keySet()) { - SupportLevel iSupportLevel = iSupports.get(fileInfo); - SupportLevel oSupportLevel = oSupports.get(fileInfo); - - if (oSupportLevel.isLessThan(iSupportLevel)) { - if (!infoLossPresent) { - msg.append(String.format( - "%-46s %s%n", "", inputFileExtension + " --> " + outputFileExtension + "\n")); - infoLossPresent = true; - } + ; - msg.append(String.format("%-36s %14s %5s%n", " " + fileInfo, iSupportLevel, oSupportLevel)); - } - } - if (infoLossPresent) { - FeatJAR.log().warning(msg.toString()); - } else { - FeatJAR.log().info("No Information Loss from " + inputFileExtension + " to " + outputFileExtension + "."); + if (!checkIfFileExtensionsValid(intputFileExtension, outputFileExtension)) { + return 2; } - } - - /** - * - * {@return information loss map that tracks how well a file extension supports any given piece of information} - */ - private Map> buildInfoLossMap() { - Map> supportMap = new HashMap<>(); - buildInfoLossMapRegisterExt( - "xml", - Map.of( - FileInfo.basicHierarchy, SupportLevel.YES, - FileInfo.subgroupHierarchy, SupportLevel.NO, - FileInfo.featureDescription, SupportLevel.YES, - FileInfo.featureAttributes, SupportLevel.YES, - FileInfo.featureCardinality, SupportLevel.NO, - FileInfo.booleanOperators, SupportLevel.YES, - FileInfo.allOperators, SupportLevel.NO, - FileInfo.parseable, SupportLevel.YES), - supportMap); - - buildInfoLossMapRegisterExt( - "uvl", - Map.of( - FileInfo.basicHierarchy, SupportLevel.YES, - FileInfo.subgroupHierarchy, SupportLevel.YES, - FileInfo.featureDescription, SupportLevel.YES, - FileInfo.featureAttributes, SupportLevel.YES, - FileInfo.featureCardinality, SupportLevel.YES, - FileInfo.booleanOperators, SupportLevel.YES, - FileInfo.allOperators, SupportLevel.YES, - FileInfo.parseable, SupportLevel.YES), - supportMap); - - buildInfoLossMapRegisterExt( - "dot", - Map.of( - FileInfo.basicHierarchy, SupportLevel.YES, - FileInfo.subgroupHierarchy, SupportLevel.YES, - FileInfo.featureDescription, SupportLevel.YES, - FileInfo.featureAttributes, SupportLevel.YES, - FileInfo.featureCardinality, SupportLevel.YES, - FileInfo.booleanOperators, SupportLevel.YES, - FileInfo.allOperators, SupportLevel.YES, - FileInfo.parseable, SupportLevel.NO), - supportMap); - - // if user forgot to set FileInfos: Support Level is automatically set to NONE - for (String ext : supportMap.keySet()) { - for (FileInfo fileInfo : FileInfo.values()) { - supportMap.get(ext).putIfAbsent(fileInfo, SupportLevel.NO); - } + // --input and --typeTXT allow for conflicting file formats to be specified, in that case a warning is printed. + // In that case format of typeTXT is prioritized. + if (outputFileExtension != "list" && optionParser.getResult(TYPE_TXT).isPresent()) { + FeatJAR.log() + .warning("Conflicting command line options: " + outputFileExtension.toLowerCase() + + " file type in Path and .txt file type due to --typeTXT.\n " + + "Continuing to write with " + + optionParser.getResult(TYPE_TXT).get().description + " format into ." + + outputFileExtension.toLowerCase() + " file."); + FeatJAR.log() + .warning( + "Once written, data cannot be read from a txt file. Do not delete source file for this conversion."); } - return supportMap; - } + // loading list from input path + BooleanAssignmentList list = IO.load( + optionParser.getResult(INPUT_OPTION).orElseThrow(), BooleanAssignmentListFormats.getInstance()) + .get(); - /** - * Reinforces correct addition of infoLossMap entries - * @param extension file extension that will be added - * @param fileInfos pieces of file information as described in FileInfo enum - * @param supportMap the information loss map that's being updated - */ - private void buildInfoLossMapRegisterExt( - String extension, - Map fileInfos, - Map> supportMap) { - if (fileInfos.size() != FileInfo.values().length) { - FeatJAR.log() - .error("Info Loss Map: " + extension - + " was added with too many or too few FileInfos. Skipping this extension."); - return; - } - supportMap.put(extension, new EnumMap<>(fileInfos)); + return saveFile(optionParser.getResult(OUTPUT_OPTION).orElseThrow(), list, optionParser.get(OVERWRITE)); } /** @@ -381,112 +181,43 @@ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { } /** - * Attempts to extract a feature model from the input file. - * @param optionParser holds the command line parameters - * @return Feature Model read out from input file. Will be null on failure. + * Saves the opened BooleanAssignmentList as a different desired BooleanAssignmentList file. Automatically detects the appropriate format. Does error handling. + * @param outputPath Full path to output file including extension. + * @param inputList BooleanAssignmentList to be saved into the output file. + * @param overwriteDemanded flag that decides whether existing output file with the same name should be overwritten. + * @return 0 on success + * */ - private Result inputParser(OptionList optionParser) { - Path inputPath = optionParser.getResult(INPUT_OPTION).orElseThrow(); - Result load = null; - try { - load = IO.load(inputPath, BooleanAssignmentListFormats.getInstance()); - //load.get(); + public int saveFile(Path outputPath, BooleanAssignmentList inputList, boolean overwriteDemanded) { + + // Dynamically gathers corresponding BooleanAssignmentListFormat for given file extension. + Optional> outputFormats = + BooleanAssignmentListFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .filter(formatTemp -> + Objects.equals(IO.getFileExtension(outputPath), formatTemp.getFileExtension())) + .findFirst(); + try { + if (Files.exists(outputPath)) { + if (overwriteDemanded) { + FeatJAR.log().info("File already present at: " + outputPath + ". Continuing to overwrite File."); + } else { + FeatJAR.log() + .error("Saving list in File unsuccessful: File already present at: " + outputPath + + ". To overwrite present file add --overwrite"); + return 3; + } + } + IO.save(inputList, outputPath, outputFormats.get()); } catch (Exception e) { - FeatJAR.log().error(e.getMessage()); + FeatJAR.log().error(e); } - return load; - } + FeatJAR.log().message("Output list successfully saved at: " + outputPath); - /** - * Saves the read feature model as the desired output file. Automatically fetches the appropriate format. Does error handling. - * @param outputPath Full path to output file. - * @param model Feature Model to be saved into the output file. - * @param outputFileExtension extension of the output file. Used to fetch appropriate format. - * @param overWriteOutputFile flag that decides whether existing output files with the same name should be overwritten. - * @return 0 on success - * 2 if an input/output file type is invalid - * 4 if a file is already present at output path and no overwrite is specified - * 5 on IOException - */ - public int saveFile(Path outputPath, BooleanAssignmentList model, String outputFileExtension, boolean overWriteOutputFile) { - IFormat format; - - Optional> outputFormats = BooleanAssignmentListFormats.getInstance().getExtensions().stream() - .filter(IFormat::supportsWrite) - .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) - .findFirst(); - - if (outputFormats.isEmpty() && !outputFileExtension.equals("TXT") && !outputFileExtension.equals("SIMPLETXT")) { - FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); - return 2; - } else if (outputFileExtension.equals("TXT")){ - outputPath = changeOutputExtension(outputPath, "txt"); - format = new BooleanAssignmentListTextFormat(); - } else if (outputFileExtension.equals("SIMPLETXT")) { - outputPath = changeOutputExtension(outputPath, "txt"); - format = new BooleanAssignmentListSimpleTextFormat(); - } else { - format = outputFormats.get(); - } - try { - if(Files.exists(outputPath)) { - if (overWriteOutputFile) { - FeatJAR.log().info("File already present at: \" + outputPath + \". Continuing to overwrite File."); - } else { - FeatJAR.log() - .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath - + ". To overwrite present file add --overwrite"); - return 4; - } - } - IO.save(model, outputPath, format); - - } catch (Exception e) { - FeatJAR.log().error(e); - } - FeatJAR.log().message("Output model saved at: " + outputPath); - -// IFormat format; -// -// Optional> outputFormats = FeatureModelFormats.getInstance().getExtensions().stream() -// .filter(IFormat::supportsWrite) -// .filter(formatTemp -> Objects.equals(outputFileExtension, formatTemp.getFileExtension())) -// .findFirst(); -// if (outputFormats.isEmpty()) { -// FeatJAR.log().error("Unsupported output file extension: " + outputFileExtension); -// return 2; -// } else { -// format = outputFormats.get(); -// } -// -// try { -// if (Files.exists(outputPath)) { -// if (overWriteOutputFile) { -// FeatJAR.log().info("File already present at: " + outputPath + ". Continuing to overwrite File."); -// } else { -// FeatJAR.log() -// .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath -// + ". To overwrite present file add --overwrite"); -// return 4; -// } -// } -// IO.save(model, outputPath, format); -// -// } catch (IOException e) { -// FeatJAR.log().error(e); -// return 5; -// } -// FeatJAR.log().message("Output model saved at: " + outputPath); return 0; } - public Path changeOutputExtension(Path path, String extension) { - String s = path.toString().replace(IO.getFileExtension(path), extension.toLowerCase()); - return Paths.get(s); - } - - /** * * {@return brief description of this class} diff --git a/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java new file mode 100644 index 00000000..8d7f654c --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java @@ -0,0 +1,406 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.cli; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import de.featjar.base.FeatJAR; +import de.featjar.base.io.IO; +import de.featjar.formula.assignment.BooleanAssignmentList; +import de.featjar.formula.io.BooleanAssignmentListFormats; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import org.junit.jupiter.api.Test; + +/** + * @author Knut & Kilian + */ +public class ConfigurationFormatConversionTest { + + /** + * Tests whether conversion from CSV to other formats produces files with the same content + * + */ + @Test + void csvToOtherFormatsTest() throws IOException { + + String inputPath = + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.csv"; + + String outputPath = "list_csvToDimacs.dimacs"; + int exit_code = FeatJAR.runTest( + "configurationFormatConversion", "--input", inputPath, "--output", outputPath, "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + + outputPath = "list_csvToBinary.bin"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", "--input", inputPath, "--output", outputPath, "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + + outputPath = "list_csvToList.list"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", + "--input", + inputPath, + "--output", + outputPath, + "--typeTXT", + "default_txt", + "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + Files.deleteIfExists(Paths.get(outputPath)); + + outputPath = "list_csvToSimpleList.list"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", + "--input", + inputPath, + "--output", + outputPath, + "--typeTXT", + "simple_txt", + "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + Files.deleteIfExists(Paths.get(outputPath)); + + // for additional testing the formats that can be parsed are compared to the original list + FeatJAR.initialize(); + BooleanAssignmentList list_expected = IO.load(Paths.get(inputPath), BooleanAssignmentListFormats.getInstance()) + .get(); + BooleanAssignmentList list_csvToDimacs = IO.load( + Paths.get("list_csvToDimacs.dimacs"), BooleanAssignmentListFormats.getInstance()) + .get(); + BooleanAssignmentList list_csvToBinary = IO.load( + Paths.get("list_csvToBinary.bin"), BooleanAssignmentListFormats.getInstance()) + .get(); + FeatJAR.deinitialize(); + + assertEquals(list_csvToDimacs.toString(), list_expected.toString()); + assertEquals(list_csvToBinary.toString(), list_expected.toString()); + Files.deleteIfExists(Paths.get("list_csvToDimacs.dimacs")); + Files.deleteIfExists(Paths.get("list_csvToBinary.bin")); + } + + /** + * Tests whether conversion from DIMACS to other formats produces files with the same content + * + */ + @Test + void dimacsToOtherFormatsTest() throws IOException { + + String inputPath = + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.dimacs"; + + String outputPath = "list_dimacsToCSV.csv"; + int exit_code = FeatJAR.runTest( + "configurationFormatConversion", "--input", inputPath, "--output", outputPath, "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + + outputPath = "list_dimacsToBinary.bin"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", "--input", inputPath, "--output", outputPath, "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + + outputPath = "list_dimacsToList.list"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", + "--input", + inputPath, + "--output", + outputPath, + "--typeTXT", + "default_txt", + "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + Files.deleteIfExists(Paths.get(outputPath)); + + outputPath = "list_dimacsToSimpleList.list"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", + "--input", + inputPath, + "--output", + outputPath, + "--typeTXT", + "simple_txt", + "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + Files.deleteIfExists(Paths.get(outputPath)); + + // for additional testing the formats that can be parsed are compared to the original list + FeatJAR.initialize(); + BooleanAssignmentList list_expected = IO.load(Paths.get(inputPath), BooleanAssignmentListFormats.getInstance()) + .get(); + BooleanAssignmentList list_dimacsToCSV = IO.load( + Paths.get("list_dimacsToCSV.csv"), BooleanAssignmentListFormats.getInstance()) + .get(); + BooleanAssignmentList list_dimacsToBinary = IO.load( + Paths.get("list_dimacsToBinary.bin"), BooleanAssignmentListFormats.getInstance()) + .get(); + FeatJAR.deinitialize(); + + assertEquals(list_dimacsToCSV.toString(), list_expected.toString()); + assertEquals(list_dimacsToBinary.toString(), list_expected.toString()); + Files.deleteIfExists(Paths.get("list_dimacsToCSV.csv")); + Files.deleteIfExists(Paths.get("list_dimacsToBinary.bin")); + } + + /** + * Tests whether conversion from binary to other formats produces files with the same content + * + */ + @Test + void binaryToOtherFormatsTest() throws IOException { + + String inputPath = + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.bin"; + + String outputPath = "list_binaryToCSV.csv"; + int exit_code = FeatJAR.runTest( + "configurationFormatConversion", "--input", inputPath, "--output", outputPath, "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + + outputPath = "list_binaryToDimacs.dimacs"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", "--input", inputPath, "--output", outputPath, "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + + outputPath = "list_binaryToList.list"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", + "--input", + inputPath, + "--output", + outputPath, + "--typeTXT", + "default_txt", + "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + Files.deleteIfExists(Paths.get(outputPath)); + + outputPath = "list_binaryToSimpleList.list"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", + "--input", + inputPath, + "--output", + outputPath, + "--typeTXT", + "simple_txt", + "--overwrite"); + assertEquals(0, exit_code); + assertTrue(new File(outputPath).exists()); + Files.deleteIfExists(Paths.get(outputPath)); + + // for additional testing the formats that can be parsed are compared to the original list + FeatJAR.initialize(); + BooleanAssignmentList list_expected = IO.load(Paths.get(inputPath), BooleanAssignmentListFormats.getInstance()) + .get(); + BooleanAssignmentList list_binaryToCSV = IO.load( + Paths.get("list_binaryToCSV.csv"), BooleanAssignmentListFormats.getInstance()) + .get(); + BooleanAssignmentList list_binaryToDimacs = IO.load( + Paths.get("list_binaryToDimacs.dimacs"), BooleanAssignmentListFormats.getInstance()) + .get(); + FeatJAR.deinitialize(); + + assertEquals(list_binaryToCSV.toString(), list_expected.toString()); + assertEquals(list_binaryToDimacs.toString(), list_expected.toString()); + Files.deleteIfExists(Paths.get("list_binaryToCSV.csv")); + Files.deleteIfExists(Paths.get("list_binaryToDimacs.dimacs")); + } + + /** + * Tests ... + * + */ + @Test + void roundTripTest() throws IOException { + + String OriginalInputPath = + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.csv"; + + // csv -> binary + String outputPath = "list_csvToBinaryRoundTrip.bin"; + int exit_code = FeatJAR.runTest( + "configurationFormatConversion", + "--input", + OriginalInputPath, + "--output", + outputPath, + "--typeTXT", + "simple_txt", + "--overwrite"); + String inputPath = outputPath; + assertEquals(0, exit_code); + + // binary -> dimacs + outputPath = "list_binaryToDimacsRoundTrip.dimacs"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", + "--input", + inputPath, + "--output", + outputPath, + "--typeTXT", + "simple_txt", + "--overwrite"); + inputPath = outputPath; + assertEquals(0, exit_code); + + // dimacs -> csv + outputPath = "list_dimacsToCSVRoundTrip.csv"; + exit_code = FeatJAR.runTest( + "configurationFormatConversion", + "--input", + inputPath, + "--output", + outputPath, + "--typeTXT", + "simple_txt", + "--overwrite"); + assertEquals(0, exit_code); + + FeatJAR.initialize(); + BooleanAssignmentList list_expected = IO.load( + Paths.get(OriginalInputPath), BooleanAssignmentListFormats.getInstance()) + .get(); + BooleanAssignmentList list_final = IO.load(Paths.get(outputPath), BooleanAssignmentListFormats.getInstance()) + .get(); + assertEquals(list_final.toString(), list_expected.toString()); + FeatJAR.deinitialize(); + + Files.deleteIfExists(Paths.get("list_csvToBinaryRoundTrip.bin")); + Files.deleteIfExists(Paths.get("list_binaryToDimacsRoundTrip.dimacs")); + Files.deleteIfExists(Paths.get("list_dimacsToCSVRoundTrip.csv")); + } + + /** + * Tests ... + * + */ + @Test + void errorHandlingTest() throws IOException { + + String OriginalInputPath = + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList."; + String OriginalInputPathCSV = OriginalInputPath + "csv"; + String OriginalInputPathTXT = OriginalInputPath + "txt"; // invalid + String OriginalInputPathBIN = OriginalInputPath + "bin"; + String OriginalInputPathXML = OriginalInputPath + "xml"; // invalid + + String outputPath = "list_csvToBinaryRoundTrip."; + String outputPathCSV = outputPath + "csv"; + String outputPathBIN = outputPath + "bin"; + String outputPathXML = outputPath + "xml"; // invalid + + assertEquals( + 1, + FeatJAR.runTest( + "configurationFormatConversion", + "--input", + "--output", + outputPathCSV, + "--typeTXT", + "simple_txt", + "--overwrite")); // missing input path + assertEquals( + 1, + FeatJAR.runTest( + "configurationFormatConversion", + "--input", + OriginalInputPathCSV, + "--output", + "--typeTXT", + "simple_txt", + "--overwrite")); // missing output path + + assertEquals( + 2, + FeatJAR.runTest( + "configurationFormatConversion", + "--input", + OriginalInputPathXML, + "--output", + outputPathBIN, + "--typeTXT", + "simple_txt", + "--overwrite")); // xml is not a supported input type + assertEquals( + 2, + FeatJAR.runTest( + "configurationFormatConversion", + "--input", + OriginalInputPathTXT, + "--output", + outputPathBIN, + "--typeTXT", + "simple_txt", + "--overwrite")); // txt is not a supported input type + assertEquals( + 2, + FeatJAR.runTest( + "configurationFormatConversion", + "--input", + OriginalInputPathBIN, + "--output", + outputPathXML, + "--typeTXT", + "simple_txt", + "--overwrite")); // xml is not a supported output type + + assertEquals( + 0, + FeatJAR.runTest( + "configurationFormatConversion", + "--input", + OriginalInputPathBIN, + "--output", + outputPathCSV, + "--typeTXT", + "simple_txt", + "--overwrite")); // creating file for next assertion + assertEquals( + 3, + FeatJAR.runTest( + "configurationFormatConversion", + "--input", + OriginalInputPathBIN, + "--output", + outputPathCSV, + "--typeTXT", + "simple_txt")); // failure to overwrite file because --overwrite is missing + } +} From 8c83a33efb71fb088312ba631c90d08da0f96a47 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 16:11:25 +0200 Subject: [PATCH 191/257] test: added tests for operator constraints --- .gitignore | 1 + .../model/analysis/visualization/Testtmp.java | 3 +- .../VisualizeFeatureModelStatsTest.java | 52 +++++++++++-------- 3 files changed, 32 insertions(+), 24 deletions(-) diff --git a/.gitignore b/.gitignore index 308f9222..44fb4716 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,4 @@ /bin/ /model_invalidInput.dot /export.pdf +/src/test/java/de/featjar/feature/model/visualization/model.pdf diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java index 81617099..4a80048e 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java @@ -113,7 +113,8 @@ public static void main(String[] args) throws Exception { //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(generateEmptyTree()); //viz.displayChart(); - viz2.displayChart(); + Integer retval = viz2.displayChart(); + System.out.println(retval); } } diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index bdaa5f7e..c348fee0 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -7,6 +7,7 @@ import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.visualization.VisualizeConstraintOperatorDistribution; import de.featjar.feature.model.analysis.visualization.VisualizeGroupDistribution; import de.featjar.feature.model.cli.PrintStatistics; import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; @@ -22,7 +23,7 @@ public class VisualizeFeatureModelStatsTest { AnalysisTree bigTree = getBigAnalysisTree(); AnalysisTree mediumTree = getMediumAnalysisTree(); - String defaultExportName = "src/test/java/de/featjar/feature/model/visualization/model.xml"; + String defaultExportName = "src/test/java/de/featjar/feature/model/visualization/model.pdf"; /** * Helper function. @@ -96,7 +97,6 @@ public AnalysisTree getMediumAnalysisTree() { return analysisTreeFromFeatureModel(buildMediumFeatureModel()); } - // todo auch Tests für die andere stats art mit constraints bla // todo Tests für FeatureModels mit mehreren Bäumen -> need custom feature model /** @@ -104,34 +104,40 @@ public AnalysisTree getMediumAnalysisTree() { */ @Test void regularLivePreview() { - VisualizeGroupDistribution viz; - - viz = new VisualizeGroupDistribution(mediumTree); - assertEquals(0, viz.displayChart()); - - viz = new VisualizeGroupDistribution(bigTree); - assertEquals(0, viz.displayChart()); + VisualizeGroupDistribution vizGroup; + vizGroup = new VisualizeGroupDistribution(mediumTree); + assertEquals(0, vizGroup.displayChart()); + vizGroup = new VisualizeGroupDistribution(bigTree); + assertEquals(0, vizGroup.displayChart()); + + VisualizeConstraintOperatorDistribution vizOpDis; + vizOpDis = new VisualizeConstraintOperatorDistribution(bigTree); + assertEquals(0, vizOpDis.displayChart()); } @Test void pdfValidIndex() { - VisualizeGroupDistribution viz; - - viz = new VisualizeGroupDistribution(mediumTree); - assertEquals(0, viz.exportChartToPDF(0, defaultExportName)); - - viz = new VisualizeGroupDistribution(bigTree); - assertEquals(0, viz.exportChartToPDF(0, defaultExportName)); + VisualizeGroupDistribution vizGroup; + vizGroup = new VisualizeGroupDistribution(mediumTree); + assertEquals(0, vizGroup.exportChartToPDF(0, defaultExportName)); + vizGroup = new VisualizeGroupDistribution(bigTree); + assertEquals(0, vizGroup.exportChartToPDF(0, defaultExportName)); + + VisualizeConstraintOperatorDistribution vizOpDis; + vizOpDis = new VisualizeConstraintOperatorDistribution(bigTree); + assertEquals(0, vizOpDis.exportChartToPDF(0, defaultExportName)); } @Test void pdfInvalidIndex() { - VisualizeGroupDistribution viz; - - viz = new VisualizeGroupDistribution(mediumTree); - assertEquals(1, viz.exportChartToPDF(99, defaultExportName)); - - viz = new VisualizeGroupDistribution(bigTree); - assertEquals(1, viz.exportChartToPDF(99, defaultExportName)); + VisualizeGroupDistribution vizGroup; + vizGroup = new VisualizeGroupDistribution(mediumTree); + assertEquals(1, vizGroup.exportChartToPDF(99, defaultExportName)); + vizGroup = new VisualizeGroupDistribution(bigTree); + assertEquals(1, vizGroup.exportChartToPDF(99, defaultExportName)); + + VisualizeConstraintOperatorDistribution vizOpDis; + vizOpDis = new VisualizeConstraintOperatorDistribution(bigTree); + assertEquals(1, vizOpDis.exportChartToPDF(99, defaultExportName)); } } From 218287789fb8c68266a0f6346472874cb5d054f0 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Wed, 15 Oct 2025 16:42:33 +0200 Subject: [PATCH 192/257] feat: calculated commonality for deselected and undefined cases --- .../model/analysis/ComputeUniformity.java | 85 +++++++++++++++--- .../model/analysis/SamplePropertiesTest.java | 88 ++++++++++++------- 2 files changed, 128 insertions(+), 45 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java index 3df03451..4e7349a8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -88,8 +88,12 @@ public Result> compute(List dependencyList, String assignmentsSamplePrefix = "_AssignmentsSample"; for (String varName : fmVariableMap.getVariableNames()) { - returnedMap.put(varName + featureModelPrefix, (float) 0); - returnedMap.put(varName + assignmentsSamplePrefix, (float) 0); + returnedMap.put(varName + featureModelPrefix + "_selected", (float) 0); + returnedMap.put(varName + assignmentsSamplePrefix + "_selected", (float) 0); + returnedMap.put(varName + featureModelPrefix + "_deselected", (float) 0); + returnedMap.put(varName + assignmentsSamplePrefix + "_deselected", (float) 0); + returnedMap.put(varName + featureModelPrefix + "_undefined", (float) 0); + returnedMap.put(varName + assignmentsSamplePrefix + "_undefined", (float) 0); } // Calculate the number of valid configurations per feature in the full featureModel. @@ -101,7 +105,24 @@ public Result> compute(List dependencyList, .map(ComputeNNFFormula::new) .compute(); returnedMap.replace( - varName + featureModelPrefix, + varName + featureModelPrefix + "_selected", + Computations.of(NNFFormula) + .map(ComputeCNFFormula::new) + .map(ComputeSolutionCount::new) + .compute() + .floatValue()); + } + + // Calculate the number of valid configurations per feature in the full featureModel. + for (String varName : fmVariableMap.getVariableNames()) { + Reference currentFormula = + new Reference(new And((IFormula) fmFormula.getChildren().get(0), new Not(new Literal(varName)))); + currentFormula.setFreeVariables(((Reference) fmFormula).getFreeVariables()); + IFormula NNFFormula = Computations.of((IFormula) currentFormula) + .map(ComputeNNFFormula::new) + .compute(); + returnedMap.replace( + varName + featureModelPrefix + "_deselected", Computations.of(NNFFormula) .map(ComputeCNFFormula::new) .map(ComputeSolutionCount::new) @@ -113,12 +134,16 @@ public Result> compute(List dependencyList, int assignmentSolutionsCount = 0; for (BooleanAssignment booleanAssignment : booleanAssignmentList.getAll()) { LinkedList allLiterals = new LinkedList(); - List currentAssignmentVariables = new LinkedList(); + List currentSelectedAssignmentVariables = new LinkedList(); + List currentDeselectedAssignmentVariables = new LinkedList(); for (int index : booleanAssignment.get()) { if (fmVariableMap.get(index).isPresent()) { allLiterals.add(new Literal(fmVariableMap.get(index).get())); - currentAssignmentVariables.add(fmVariableMap.get(index).get()); + currentSelectedAssignmentVariables.add( + fmVariableMap.get(index).get()); } else if (fmVariableMap.get(Math.abs(index)).isPresent()) { + currentDeselectedAssignmentVariables.add( + fmVariableMap.get(Math.abs(index)).get()); allLiterals.add(new Not( new Literal(fmVariableMap.get(Math.abs(index)).get()))); } else { @@ -135,20 +160,56 @@ public Result> compute(List dependencyList, .map(ComputeSatisfiability::new) .compute()) { assignmentSolutionsCount++; - for (String key : currentAssignmentVariables) { + for (String key : currentSelectedAssignmentVariables) { + returnedMap.replace( + key + assignmentsSamplePrefix + "_selected", + returnedMap.get(key + assignmentsSamplePrefix + "_selected") + 1); + } + + for (String key : currentDeselectedAssignmentVariables) { returnedMap.replace( - key + assignmentsSamplePrefix, returnedMap.get(key + assignmentsSamplePrefix) + 1); + key + assignmentsSamplePrefix + "_deselected", + returnedMap.get(key + assignmentsSamplePrefix + "_deselected") + 1); } } } + for (String varName : fmVariableMap.getVariableNames()) { + returnedMap.replace( + varName + assignmentsSamplePrefix + "_undefined", + assignmentSolutionsCount + - returnedMap.get(varName + assignmentsSamplePrefix + "_selected") + - returnedMap.get(varName + assignmentsSamplePrefix + "_deselected")); + returnedMap.replace( + varName + featureModelPrefix + "_undefined", + solutionsCount + - returnedMap.get(varName + featureModelPrefix + "_selected") + - returnedMap.get(varName + featureModelPrefix + "_deselected")); + } + if (ANALYSIS.get(dependencyList)) { for (String varName : fmVariableMap.getVariableNames()) { - float sampleShare = returnedMap.get(varName + assignmentsSamplePrefix) / assignmentSolutionsCount; - float featureShare = returnedMap.get(varName + featureModelPrefix) / solutionsCount; - returnedMap.remove(varName + assignmentsSamplePrefix); - returnedMap.remove(varName + featureModelPrefix); - returnedMap.put(varName, sampleShare - featureShare); + float sampleShareSelected = + returnedMap.get(varName + assignmentsSamplePrefix + "_selected") / assignmentSolutionsCount; + float featureShareSelected = + returnedMap.get(varName + featureModelPrefix + "_selected") / solutionsCount; + float sampleShareDeselected = + returnedMap.get(varName + assignmentsSamplePrefix + "_deselected") / assignmentSolutionsCount; + float featureShareDeselected = + returnedMap.get(varName + featureModelPrefix + "_deselected") / solutionsCount; + float sampleShareUndefined = + returnedMap.get(varName + assignmentsSamplePrefix + "_undefined") / assignmentSolutionsCount; + float featureShareUndefined = + returnedMap.get(varName + featureModelPrefix + "_undefined") / solutionsCount; + returnedMap.remove(varName + assignmentsSamplePrefix + "_selected"); + returnedMap.remove(varName + featureModelPrefix + "_selected"); + returnedMap.remove(varName + assignmentsSamplePrefix + "_deselected"); + returnedMap.remove(varName + featureModelPrefix + "_deselected"); + returnedMap.remove(varName + assignmentsSamplePrefix + "_undefined"); + returnedMap.remove(varName + featureModelPrefix + "_undefined"); + returnedMap.put(varName + "_selected", sampleShareSelected - featureShareSelected); + returnedMap.put(varName + "_deselected", sampleShareDeselected - featureShareDeselected); + returnedMap.put(varName + "_undefined", sampleShareUndefined - featureShareUndefined); } } else { returnedMap.put("FeatureModel Valid", solutionsCount); diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index 4a0242e4..bf51f013 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -76,9 +76,9 @@ public BooleanAssignmentList createAssignmentListUniformity(FeatureModel feature variableMap, new BooleanAssignment( variableMap.get("ConfigDB").get(), + variableMap.get("API").get(), variableMap.get("Get").get(), variableMap.get("Windows").get(), - -variableMap.get("Put").get(), -variableMap.get("Delete").get(), -variableMap.get("Transactions").get(), -variableMap.get("Linux").get()), @@ -105,7 +105,25 @@ public BooleanAssignmentList createAssignmentListUniformity(FeatureModel feature -variableMap.get("Put").get(), -variableMap.get("Delete").get(), variableMap.get("Transactions").get(), - variableMap.get("Linux").get())); + variableMap.get("Linux").get()), + new BooleanAssignment( + variableMap.get("ConfigDB").get(), + -variableMap.get("API").get(), + variableMap.get("Get").get(), + variableMap.get("Windows").get(), + -variableMap.get("Put").get(), + -variableMap.get("Delete").get(), + -variableMap.get("Transactions").get(), + -variableMap.get("Linux").get()), + new BooleanAssignment( + variableMap.get("ConfigDB").get(), + variableMap.get("API").get(), + variableMap.get("Get").get(), + variableMap.get("Windows").get(), + variableMap.get("Put").get(), + -variableMap.get("Delete").get(), + -variableMap.get("Transactions").get(), + -variableMap.get("Linux").get())); return booleanAssignmentList; } @@ -190,44 +208,48 @@ public void computeUniformity() { .set(ComputeUniformity.ANALYSIS, false); HashMap result = computation.compute(); + System.out.println("Descriptive validtiy map: \n" + result); + computation.set(ComputeUniformity.ANALYSIS, true); + result = computation.compute(); + System.out.println("Commonality difference per features: \n" + result); + assertEquals(26, result.get("FeatureModel Valid")); assertEquals(2, result.get("AssignmentsSample Valid")); - assertEquals(26, result.get("ConfigDB_FeatureModel")); - assertEquals(2, result.get("ConfigDB_AssignmentsSample")); - assertEquals(26, result.get("API_FeatureModel")); - assertEquals(0, result.get("API_AssignmentsSample")); - assertEquals(26, result.get("OS_FeatureModel")); - assertEquals(0, result.get("OS_AssignmentsSample")); - assertEquals(14, result.get("Get_FeatureModel")); - assertEquals(2, result.get("Get_AssignmentsSample")); - assertEquals(16, result.get("Put_FeatureModel")); - assertEquals(1, result.get("Put_AssignmentsSample")); - assertEquals(16, result.get("Delete_FeatureModel")); - assertEquals(1, result.get("Delete_AssignmentsSample")); - assertEquals(13, result.get("Windows_FeatureModel")); - assertEquals(1, result.get("Windows_AssignmentsSample")); - assertEquals(13, result.get("Linux_FeatureModel")); - assertEquals(1, result.get("Linux_AssignmentsSample")); - assertEquals(1, result.get("Windows_AssignmentsSample")); - assertEquals(13, result.get("Linux_FeatureModel")); - assertEquals(1, result.get("Linux_AssignmentsSample")); - assertEquals(12, result.get("Transactions_FeatureModel")); - assertEquals(1, result.get("Transactions_AssignmentsSample")); + assertEquals(26, result.get("ConfigDB_FeatureModel_selected")); + assertEquals(2, result.get("ConfigDB_AssignmentsSample_selected")); + assertEquals(26, result.get("API_FeatureModel_selected")); + assertEquals(1, result.get("API_AssignmentsSample_selected")); + assertEquals(26, result.get("OS_FeatureModel_selected")); + assertEquals(0, result.get("OS_AssignmentsSample_selected")); + assertEquals(14, result.get("Get_FeatureModel_selected")); + assertEquals(2, result.get("Get_AssignmentsSample_selected")); + assertEquals(16, result.get("Put_FeatureModel_selected")); + assertEquals(1, result.get("Put_AssignmentsSample_selected")); + assertEquals(16, result.get("Delete_FeatureModel_selected")); + assertEquals(1, result.get("Delete_AssignmentsSample_selected")); + assertEquals(13, result.get("Windows_FeatureModel_selected")); + assertEquals(1, result.get("Windows_AssignmentsSample_selected")); + assertEquals(13, result.get("Linux_FeatureModel_selected")); + assertEquals(1, result.get("Linux_AssignmentsSample_selected")); + assertEquals(1, result.get("Windows_AssignmentsSample_selected")); + assertEquals(13, result.get("Linux_FeatureModel_selected")); + assertEquals(1, result.get("Linux_AssignmentsSample_selected")); + assertEquals(12, result.get("Transactions_FeatureModel_selected")); + assertEquals(1, result.get("Transactions_AssignmentsSample_selected")); assertEquals(26, result.get("FeatureModel Valid")); assertEquals(2, result.get("AssignmentsSample Valid")); - System.out.println("Descriptive validtiy map: \n" + result); computation.set(ComputeUniformity.ANALYSIS, true); result = computation.compute(); - assertEquals(0, result.get("ConfigDB")); - assertEquals(-1, result.get("API")); - assertEquals(-1, result.get("OS")); - assertEquals(((float) 2 / 2) - ((float) 14 / 26), result.get("Get")); - assertEquals(((float) 1 / 2) - ((float) 16 / 26), result.get("Put")); - assertEquals(((float) 1 / 2) - ((float) 16 / 26), result.get("Delete")); - assertEquals(0, result.get("Windows")); - assertEquals(0, result.get("Linux")); - assertEquals(((float) 1 / 2) - ((float) 12 / 26), result.get("Transactions")); + assertEquals(0, result.get("ConfigDB_selected")); + assertEquals(-(float) 1 / (float) 2, result.get("API_selected")); + assertEquals(-1, result.get("OS_selected")); + assertEquals(((float) 2 / 2) - ((float) 14 / 26), result.get("Get_selected")); + assertEquals(((float) 1 / 2) - ((float) 16 / 26), result.get("Put_selected")); + assertEquals(((float) 1 / 2) - ((float) 16 / 26), result.get("Delete_selected")); + assertEquals(0, result.get("Windows_selected")); + assertEquals(0, result.get("Linux_selected")); + assertEquals(((float) 1 / 2) - ((float) 12 / 26), result.get("Transactions_selected")); System.out.println("Commonality difference per features: \n" + result); } From 62907e294de5da6855524902199f16d711ca79a3 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Wed, 15 Oct 2025 16:43:25 +0200 Subject: [PATCH 193/257] fix: corrected behavior of json parsing/serializing. tests: fixed jsontest in printstatistics --- .../analysis/visitor/AnalysisTreeVisitor.java | 7 ++- .../transformer/AnalysisTreeTransformer.java | 23 +++++++-- .../model/cli/PrintStatisticsTest.java | 51 ++++++++++--------- 3 files changed, 50 insertions(+), 31 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java index 40e8d1ca..bedcd416 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitor.java @@ -20,6 +20,7 @@ */ package de.featjar.feature.model.analysis.visitor; +import de.featjar.base.FeatJAR; import de.featjar.base.data.Result; import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.feature.model.analysis.AnalysisTree; @@ -59,13 +60,15 @@ public TraversalAction firstVisit(List> path) { } } - if (node.getChildrenCount() == 0) { + if (node.getChildrenCount() == 0 && node.getValue() != null) { currentMap.put( node.getName(), new ArrayList(Arrays.asList( node.getName(), node.getValue().getClass().getName(), node.getValue()))); - } else { + } else if (node.getChildrenCount() != 0) { currentMap.put(node.getName(), new HashMap()); + } else { + FeatJAR.log().warning("cannot add terminal node without value"); } return TraversalAction.CONTINUE; diff --git a/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java index e242a628..fff3e3f3 100644 --- a/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java +++ b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java @@ -129,13 +129,27 @@ public static Result> jsonHashMapToTree(HashMap } if (currentElement.get(1).equals("java.lang.Double")) { - BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); - root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.doubleValue())); + if (currentElement.get(2) instanceof BigDecimal) { + BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.doubleValue())); + } else if (currentElement.get(2) instanceof Integer) { + Integer intValue = (Integer) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, intValue.doubleValue())); + } else { + return Result.empty(); + } } else if (currentElement.get(1).equals("java.lang.Integer")) { root.addChild(new AnalysisTree<>(currentKey, (int) currentElement.get(2))); } else if (currentElement.get(1).equals("java.lang.Float")) { - BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); - root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.floatValue())); + if (currentElement.get(2) instanceof BigDecimal) { + BigDecimal currentDeccimal = (BigDecimal) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, currentDeccimal.floatValue())); + } else if (currentElement.get(2) instanceof Integer) { + Integer intValue = (Integer) currentElement.get(2); + root.addChild(new AnalysisTree<>(currentKey, intValue.floatValue())); + } else { + return Result.empty(); + } } } } @@ -202,7 +216,6 @@ public static Result> yamlHashMapToTree(HashMap return Result.empty(); } if (!(currentElement.get(2) instanceof Double || currentElement.get(2) instanceof Integer)) { - System.out.println(currentElement.get(2).getClass()); FeatJAR.log() .error("The third element of an innermost element of the Map/YAML data structure " + "was not from the type String"); diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 35299a55..fb1cd222 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -30,6 +30,7 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.cli.PrintStatistics.AnalysesScope; +import de.featjar.feature.model.io.json.JSONAnalysisFormat; import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; import java.io.IOException; import java.nio.file.Files; @@ -194,30 +195,32 @@ void prettyStringBuilder() throws IOException { assertEquals(comparison, printStats.buildStringPrettyStats(testData).toString()); } // TODO implement this test once the jsonHashMapToTree() function works in AnalysisTreeTransformer - // /** - // * Testing whether JSON output creates correct file - // */ - // @Test - // void jsonOuputTest() throws IOException { - // - // int exit_code = FeatJAR.runTest( - // "printStats", - // "--input", - // "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", - // "--output", - // "model_jsonOuputTest.json", - // "--overwrite"); - // - // AnalysisTree tree = IO.load(Paths.get("model_jsonOuputTest.json"), new JSONAnalysisFormat()).get(); - // AnalysisTree tree_expected = - // IO.load(Paths.get("src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOuputTest.json"), new - // YAMLAnalysisFormat()).get(); - // - // assertEquals(tree.print(), tree_expected.print()); - // assertEquals(0, exit_code); - // - // Files.deleteIfExists(Paths.get("model_jsonOuputTest.json")); - // } + /** + * Testing whether JSON output creates correct file + */ + @Test + void jsonOuputTest() throws IOException { + + int exit_code = FeatJAR.runTest( + "printStats", + "--input", + "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", + "--output", + "model_jsonOuputTest.json", + "--overwrite"); + + AnalysisTree tree = IO.load(Paths.get("model_jsonOuputTest.json"), new JSONAnalysisFormat()) + .get(); + AnalysisTree tree_expected = IO.load( + Paths.get("src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json"), + new JSONAnalysisFormat()) + .get(); + + assertEquals(tree.print(), tree_expected.print()); + assertEquals(0, exit_code); + + Files.deleteIfExists(Paths.get("model_jsonOutputTest.json")); + } /** * Testing whether YAML output creates correct file From 9ab588bec85d0131c60601b21532eb45a4a72ff7 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 16:50:55 +0200 Subject: [PATCH 194/257] test: removed test that had confusing popups --- .gitignore | 1 + .../model/analysis/visualization/Testtmp.java | 14 +++++++- .../VisualizeFeatureModelStatsTest.java | 35 ++++++++++++------- 3 files changed, 36 insertions(+), 14 deletions(-) diff --git a/.gitignore b/.gitignore index 44fb4716..dc235c27 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ /model_invalidInput.dot /export.pdf /src/test/java/de/featjar/feature/model/visualization/model.pdf +/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java index 4a80048e..3726cd93 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java @@ -89,7 +89,7 @@ public static FeatureModel generateMediumTree() { return featureModel; } - public static void main(String[] args) throws Exception { + public static void main(String[] args) { PrintStatistics printStatistics = new PrintStatistics(); LinkedHashMap map = printStatistics.collectStats( generateMediumTree(), @@ -106,6 +106,18 @@ public static void main(String[] args) throws Exception { ); AnalysisTree bigAnalysisTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + /* + FeatureModel doubleTroubleModel = generateMediumTree(); + IFeatureTree clonedRoot = doubleTroubleModel.clone().getRoots().get(0); + doubleTroubleModel.mutate().addFeatureTreeRoot(clonedRoot); + map = printStatistics.collectStats( + doubleTroubleModel, + PrintStatistics.AnalysesScope.ALL + ); + AnalysisTree doubleTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + + */ + VisualizeGroupDistribution viz = new VisualizeGroupDistribution(bigAnalysisTree); VisualizeConstraintOperatorDistribution viz2 = new VisualizeConstraintOperatorDistribution(bigAnalysisTree); diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index c348fee0..63dde28b 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -23,6 +23,8 @@ public class VisualizeFeatureModelStatsTest { AnalysisTree bigTree = getBigAnalysisTree(); AnalysisTree mediumTree = getMediumAnalysisTree(); + AnalysisTree doubleTree = getDoubleTree(); + String defaultExportName = "src/test/java/de/featjar/feature/model/visualization/model.pdf"; /** @@ -93,36 +95,43 @@ public AnalysisTree getBigAnalysisTree () { return analysisTreeFromXML(Paths.get("src/test/java/de/featjar/feature/model/visualization/model.xml")); } + /** + * Warning: this tree does not have Constraint Operators + */ public AnalysisTree getMediumAnalysisTree() { return analysisTreeFromFeatureModel(buildMediumFeatureModel()); } - // todo Tests für FeatureModels mit mehreren Bäumen -> need custom feature model - /** - * This Test may have to be disabled or restructured if it creates confusing momentary popups on every gradle build + * {@return Feature Model with two identical trees.} */ + public AnalysisTree getDoubleTree() { + FeatureModel featureModel = buildMediumFeatureModel(); + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); + return analysisTreeFromFeatureModel(featureModel); + } + + // todo test for other polymorph display or export methods + @Test - void regularLivePreview() { + void twoPagePDFExport() { VisualizeGroupDistribution vizGroup; - vizGroup = new VisualizeGroupDistribution(mediumTree); - assertEquals(0, vizGroup.displayChart()); - vizGroup = new VisualizeGroupDistribution(bigTree); - assertEquals(0, vizGroup.displayChart()); - - VisualizeConstraintOperatorDistribution vizOpDis; - vizOpDis = new VisualizeConstraintOperatorDistribution(bigTree); - assertEquals(0, vizOpDis.displayChart()); + vizGroup = new VisualizeGroupDistribution(doubleTree); + assertEquals(2, vizGroup.getCharts().size()); + assertEquals(0, vizGroup.exportAllChartsToPDF(defaultExportName)); } @Test - void pdfValidIndex() { + void pdfValidIndexGroupDistribution() { VisualizeGroupDistribution vizGroup; vizGroup = new VisualizeGroupDistribution(mediumTree); assertEquals(0, vizGroup.exportChartToPDF(0, defaultExportName)); vizGroup = new VisualizeGroupDistribution(bigTree); assertEquals(0, vizGroup.exportChartToPDF(0, defaultExportName)); + } + @Test + void pdfValidIndexOperatorDistribution() { VisualizeConstraintOperatorDistribution vizOpDis; vizOpDis = new VisualizeConstraintOperatorDistribution(bigTree); assertEquals(0, vizOpDis.exportChartToPDF(0, defaultExportName)); From ea5650a41c095a98ede1935e121494cd7ec41198 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Wed, 15 Oct 2025 16:54:27 +0200 Subject: [PATCH 195/257] wip. last push of day --- .../cli/ConfigurationFormatConversion.java | 19 +++++++++++++------ .../ConfigurationFormatConversionTest.java | 6 +++--- 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 39eeb521..9581a0db 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -43,7 +43,7 @@ public class ConfigurationFormatConversion implements ICommand { public enum TypeTXT { - SIMPLE_TXT("simple text"), + SIMPLE_TXT("simple_text"), DEFAULT_TXT("text"); public final String description; @@ -69,18 +69,25 @@ public String toString() { .filter(IFormat::supportsWrite) .map(IFormat::getFileExtension) .collect(Collectors.toList()); + + private static final List supportedOutputFileNames = + BooleanAssignmentListFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .map(f -> f.getName() + " (" + f.getFileExtension() + ")") + .collect(Collectors.toList()); public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions); public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) - .setDescription("Path to output file. Accepted File Types: " + supportedOutputFileExtensions); + .setDescription("Path to output file. Accepted File Types: " + supportedOutputFileNames); public static final Option OVERWRITE = Option.newFlag("overwrite").setDescription("Overwrite existing file at output path."); - public static final Option TYPE_TXT = Option.newEnumOption("typeTXT", TypeTXT.class) - .setDescription("Specification necessary if output is desired to be .txt"); + public static final Option FORMAT_TYPE = Option.newEnumOption("format", TypeTXT.class) + .setDescription("Specification necessary if output is desired to be .txt"); //TODO change to list object supportedOutputFileExtensions. + // man soll extension angeben können und in die datei, die bei output angegeben sind unabhängig von der angegebenen extension im output path speichern können /** * @return all options registered for the calling class. @@ -119,12 +126,12 @@ public int run(OptionList optionParser) { // --input and --typeTXT allow for conflicting file formats to be specified, in that case a warning is printed. // In that case format of typeTXT is prioritized. - if (outputFileExtension != "list" && optionParser.getResult(TYPE_TXT).isPresent()) { + if (outputFileExtension != "list" && optionParser.getResult(FORMAT_TYPE).isPresent()) { FeatJAR.log() .warning("Conflicting command line options: " + outputFileExtension.toLowerCase() + " file type in Path and .txt file type due to --typeTXT.\n " + "Continuing to write with " - + optionParser.getResult(TYPE_TXT).get().description + " format into ." + + optionParser.getResult(FORMAT_TYPE).get().description + " format into ." + outputFileExtension.toLowerCase() + " file."); FeatJAR.log() .warning( diff --git a/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java index 8d7f654c..099f53b3 100644 --- a/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java @@ -317,14 +317,14 @@ void errorHandlingTest() throws IOException { String OriginalInputPath = "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList."; String OriginalInputPathCSV = OriginalInputPath + "csv"; - String OriginalInputPathTXT = OriginalInputPath + "txt"; // invalid + String OriginalInputPathTXT = OriginalInputPath + "txt"; // invalid input path String OriginalInputPathBIN = OriginalInputPath + "bin"; - String OriginalInputPathXML = OriginalInputPath + "xml"; // invalid + String OriginalInputPathXML = OriginalInputPath + "xml"; // invalid input path String outputPath = "list_csvToBinaryRoundTrip."; String outputPathCSV = outputPath + "csv"; String outputPathBIN = outputPath + "bin"; - String outputPathXML = outputPath + "xml"; // invalid + String outputPathXML = outputPath + "xml"; // invalid output path assertEquals( 1, From e98cfe7862eccf364d24925c9a4efcd78b5bf9bd Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 18:16:48 +0200 Subject: [PATCH 196/257] feat: added basic font customization --- .../visualization/AVisualizeFeatureModelStats.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index e66a615b..df754999 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -3,7 +3,6 @@ import de.featjar.base.FeatJAR; import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; -import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D; @@ -17,9 +16,11 @@ import org.knowm.xchart.internal.chartpart.Chart; import javax.swing.*; +import java.awt.*; import java.awt.geom.AffineTransform; import java.io.IOException; import java.util.*; +import java.util.List; import java.util.stream.Collectors; /** @@ -37,6 +38,9 @@ public abstract class AVisualizeFeatureModelStats { private Integer chartWidth = 800; private Integer chartHeight = 600; + protected Font fontTitle = new Font(Font.SANS_SERIF, Font.BOLD, 30); + protected Font fontLabels = new Font(Font.SANS_SERIF, Font.BOLD, 20); + protected Font fontLegend = new Font(Font.SANS_SERIF, Font.PLAIN, 20); public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { this.analysisTree = analysisTree; @@ -181,6 +185,9 @@ private HashMap extractAnalysisMap() { .height(getHeight()) .build(); chart.setTitle(treeKey); + chart.getStyler().setChartTitleFont(fontTitle); + chart.getStyler().setLabelsFont(fontLabels); + chart.getStyler().setLegendFont(fontLegend); HashMap treeData = analysisTreeData.get(treeKey); treeData.forEach((key, value) -> chart.addSeries(key, (Integer) value)); From 80387b354d350549f25108dc2130487fe38d6508 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Wed, 15 Oct 2025 18:18:05 +0200 Subject: [PATCH 197/257] fix: used correct warning method for pdfexport with empty chart list --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index df754999..5738133a 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -311,7 +311,7 @@ public int exportChartToPDF(Chart chart, String path) { * @return 0 on success, 1 on general errors, 2 if there are no internal charts to use. */ public int exportChartToPDF(Integer index, String path) { - if (chartsAreEmptyDisplay()) {return 2;} + if (chartsAreEmptyPDF()) {return 2;} try { return exportChartToPDF(charts.get(index), path); } catch (IndexOutOfBoundsException e) { @@ -326,7 +326,7 @@ public int exportChartToPDF(Integer index, String path) { * @return 0 on success, 1 on general errors, 2 if there are no internal charts to use. */ public int exportChartToPDF(String path) { - if (chartsAreEmptyDisplay()) {return 2;} + if (chartsAreEmptyPDF()) {return 2;} return exportChartToPDF(0, path); } @@ -363,7 +363,7 @@ public int exportAllChartsToPDF(List> charts, String path) { * @return 0 on success, 1 on general errors, 2 if there are no internal charts to use. */ public int exportAllChartsToPDF(String path) { - if (chartsAreEmptyDisplay()) {return 2;} + if (chartsAreEmptyPDF()) {return 2;} return exportAllChartsToPDF(charts, path); } From d31c0a4892a716417cc8f1be148845aa4ffbb12a Mon Sep 17 00:00:00 2001 From: Valentin Date: Wed, 15 Oct 2025 19:37:25 +0200 Subject: [PATCH 198/257] feat: Added buildBoxCharts functionality to AVisualFeatureModelStats and implemented the AverageNumberOfChildren Visualizer --- .../AVisualizeFeatureModelStats.java | 33 +++++++++++++++++-- .../model/analysis/visualization/Testtmp.java | 7 ++-- .../VisualizeAverageNumberOfChildren.java | 25 ++++++++++++++ 3 files changed, 59 insertions(+), 6 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index e66a615b..c4b10b60 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -11,9 +11,7 @@ import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageContentStream; import org.apache.pdfbox.util.Matrix; -import org.knowm.xchart.PieChart; -import org.knowm.xchart.PieChartBuilder; -import org.knowm.xchart.SwingWrapper; +import org.knowm.xchart.*; import org.knowm.xchart.internal.chartpart.Chart; import javax.swing.*; @@ -190,6 +188,35 @@ private HashMap extractAnalysisMap() { return charts; } + /** + * Premade builder for box charts that you can use when implementing buildCharts(). + * @return list containing one chart per tree in the feature model + */ + protected ArrayList> buildBoxCharts() { + ArrayList> charts = new ArrayList<>(); + // in averagenumberofchildren only one double is provided instead of an double array, so I used testdata to test die BoxPlot + // for example not the average number of children, but the number of children for every node as a double or int array is needed to plot a boxplot for the average number of children + double[] testdata = {0.9, 1.5, 2.22}; + + for (String treeKey : this.analysisTreeData.keySet()) { + // Momentan bauen wir pro Tree einen Chart mit einer Box mit mehreren Werten + // Wäre es für Boxplots nicht sinnvoller einen Chart für alle Trees zu machen, + // mit je eine Box pro Tree? + BoxChart chart = new BoxChartBuilder() + .width(getWidth()) + .height(getHeight()) + .build(); + chart.setTitle(treeKey); + + HashMap treeData = analysisTreeData.get(treeKey); + + treeData.forEach((key, value) -> chart.addSeries(key, testdata)); + + charts.add(chart); + } + return charts; + } + /** * Creates a live preview pop-up window of a chart. * @param chart the chart that will be displayed diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java index 3726cd93..731037a8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java @@ -119,13 +119,14 @@ public static void main(String[] args) { */ - VisualizeGroupDistribution viz = new VisualizeGroupDistribution(bigAnalysisTree); - VisualizeConstraintOperatorDistribution viz2 = new VisualizeConstraintOperatorDistribution(bigAnalysisTree); + //VisualizeGroupDistribution viz = new VisualizeGroupDistribution(bigAnalysisTree); + //VisualizeConstraintOperatorDistribution viz2 = new VisualizeConstraintOperatorDistribution(bigAnalysisTree); + VisualizeAverageNumberOfChildren viz3 = new VisualizeAverageNumberOfChildren(bigAnalysisTree); //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(createDefaultTree()); //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(generateEmptyTree()); //viz.displayChart(); - Integer retval = viz2.displayChart(); + Integer retval = viz3.displayChart(); System.out.println(retval); } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java new file mode 100644 index 00000000..b78e528c --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java @@ -0,0 +1,25 @@ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import org.knowm.xchart.internal.chartpart.Chart; + +import java.util.ArrayList; + +public class VisualizeAverageNumberOfChildren extends AVisualizeFeatureModelStats { + public VisualizeAverageNumberOfChildren(AnalysisTree analysisTree) { super(analysisTree); } + + /** + * {@return String key used to fetch data from the Analysis Tree.} + */ + @Override + protected String getAnalysisTreeDataName() { return "Average Number of Children"; } + + /** + * Use analysisTreeData to access the data relevant for building your chart. + * There are also premade builders that you may adopt. + * + * @return list containing one chart per tree in the feature model + */ + @Override + ArrayList> buildCharts() { return buildBoxCharts(); } +} From e4d5f00a8df74b16806cef074529dce12cd80ad1 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 09:02:31 +0200 Subject: [PATCH 199/257] feat | test: changing chart height or width now rebuilds the charts with those new settings. Also added tests for this. refactor: exported styler from buildPieCharts to general method for better reusability --- .../AVisualizeFeatureModelStats.java | 31 ++++++++++++++++--- .../VisualizeFeatureModelStatsTest.java | 26 ++++++++++++++++ 2 files changed, 53 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index e2176a8f..d2ce46a7 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -32,7 +32,7 @@ public abstract class AVisualizeFeatureModelStats { final protected AnalysisTree analysisTree; protected LinkedHashMap> analysisTreeData; - final protected ArrayList> charts; + protected ArrayList> charts; private Integer chartWidth = 800; private Integer chartHeight = 600; @@ -51,12 +51,18 @@ public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { return this.charts; } + public void setCharts(ArrayList> charts) { + this.charts = charts; + this.charts = buildCharts(); + } + public Integer getWidth() { return this.chartWidth; } public void setWidth(Integer width) { this.chartWidth = width; + this.charts = buildCharts(); } public Integer getHeight() { @@ -65,6 +71,7 @@ public Integer getHeight() { public void setHeight(Integer height) { this.chartHeight = height; + this.charts = buildCharts(); } /** @@ -183,9 +190,7 @@ private HashMap extractAnalysisMap() { .height(getHeight()) .build(); chart.setTitle(treeKey); - chart.getStyler().setChartTitleFont(fontTitle); - chart.getStyler().setLabelsFont(fontLabels); - chart.getStyler().setLegendFont(fontLegend); + defaultStyler(chart); HashMap treeData = analysisTreeData.get(treeKey); treeData.forEach((key, value) -> chart.addSeries(key, (Integer) value)); @@ -224,6 +229,24 @@ private HashMap extractAnalysisMap() { return charts; } + /** + * Styles charts with the default settings, for consistency. + */ + private void defaultStyler(Chart chart) { + chart.getStyler().setChartTitleFont(fontTitle); + chart.getStyler().setLegendFont(fontLegend); + if (chart instanceof PieChart) { + defaultStylerPieChartExtension((PieChart) chart); + } + } + + /** + * extends the defaultStyler() method with PieChart-specific calls + */ + private void defaultStylerPieChartExtension(PieChart chart) { + chart.getStyler().setLabelsFont(fontLabels); + } + /** * Creates a live preview pop-up window of a chart. * @param chart the chart that will be displayed diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index 63dde28b..56c55a11 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -149,4 +149,30 @@ void pdfInvalidIndex() { vizOpDis = new VisualizeConstraintOperatorDistribution(bigTree); assertEquals(1, vizOpDis.exportChartToPDF(99, defaultExportName)); } + + @Test + void changeChartHeight() { + VisualizeGroupDistribution vizGroup; + vizGroup = new VisualizeGroupDistribution(mediumTree); + + Integer chartHeight = vizGroup.getHeight(); + assertEquals(chartHeight, vizGroup.getCharts().get(0).getHeight()); + + chartHeight = 500; + vizGroup.setHeight(chartHeight); + assertEquals(chartHeight, vizGroup.getCharts().get(0).getHeight()); + } + + @Test + void changeChartWidth() { + VisualizeGroupDistribution vizGroup; + vizGroup = new VisualizeGroupDistribution(mediumTree); + + Integer chartWidth = vizGroup.getWidth(); + assertEquals(chartWidth, vizGroup.getCharts().get(0).getWidth()); + + chartWidth = 500; + vizGroup.setWidth(chartWidth); + assertEquals(chartWidth, vizGroup.getCharts().get(0).getWidth()); + } } From 48de0562fcaa1c5cffe7dce0dd770fa43932bebc Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 09:05:46 +0200 Subject: [PATCH 200/257] fix: sample feature model now set optional/mandatory feature settings correctly --- .../model/visualization/VisualizeFeatureModelStatsTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index 56c55a11..6a81d056 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -42,7 +42,7 @@ public FeatureModel buildMediumFeatureModel() { IFeature featureAPI = featureModel.mutate().addFeature("API"); IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); - treeAPI.isMandatory(); + treeAPI.mutate().makeMandatory(); IFeature featureGet = featureModel.mutate().addFeature("Get"); treeAPI.mutate().addFeatureBelow(featureGet); IFeature featurePut = featureModel.mutate().addFeature("Put"); @@ -53,7 +53,7 @@ public FeatureModel buildMediumFeatureModel() { IFeature featureOS = featureModel.mutate().addFeature("OS"); IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); - treeOS.isMandatory(); + treeOS.mutate().makeMandatory(); IFeature featureWindows = featureModel.mutate().addFeature("Windows"); treeOS.mutate().addFeatureBelow(featureWindows); IFeature featureLinux = featureModel.mutate().addFeature("Linux"); @@ -62,7 +62,7 @@ public FeatureModel buildMediumFeatureModel() { IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); - treeTransactions.isOptional(); + treeTransactions.mutate().makeOptional(); return featureModel; } From a2f648c4363e0f24e416c322143bdcf3e0e88e58 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 16 Oct 2025 10:10:08 +0200 Subject: [PATCH 201/257] test: updated the computeUniformity test --- .../model/analysis/SamplePropertiesTest.java | 90 +++++++++++++------ 1 file changed, 63 insertions(+), 27 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index bf51f013..c0644361 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -143,7 +143,6 @@ public void computeDistributionFeaturesSelectionsTest() { assertEquals(7, selectionDistribution.get("selected")); assertEquals(6, selectionDistribution.get("deselected")); assertEquals(22, selectionDistribution.get("undefined")); - System.out.println("Distribution of feature selection: \n" + selectionDistribution); } @Test @@ -175,7 +174,6 @@ public void computeFeatureCounterTest() { assertEquals(2, featureCounter.get("E_undefined")); assertEquals(2, featureCounter.get("F_undefined")); assertEquals(5, featureCounter.get("G_undefined")); - System.out.println("Distribution of feature selection per feature: \n" + featureCounter); } @Test @@ -184,7 +182,6 @@ public void computeNumberConfigurationTest() { IComputation computational = Computations.of(booleanAssignmentList).map(ComputeNumberConfigurations::new); assertEquals(5, computational.compute()); - System.out.println("Number of configurations: \n" + computational.compute()); } @Test @@ -193,7 +190,6 @@ public void computeNumberVariablesTest() { IComputation computational = Computations.of(booleanAssignmentList).map(ComputeNumberVariables::new); assertEquals(7, computational.compute()); - System.out.println("Number of variables: \n" + computational.compute()); } @Test @@ -208,49 +204,89 @@ public void computeUniformity() { .set(ComputeUniformity.ANALYSIS, false); HashMap result = computation.compute(); - System.out.println("Descriptive validtiy map: \n" + result); - computation.set(ComputeUniformity.ANALYSIS, true); - result = computation.compute(); - System.out.println("Commonality difference per features: \n" + result); - assertEquals(26, result.get("FeatureModel Valid")); - assertEquals(2, result.get("AssignmentsSample Valid")); assertEquals(26, result.get("ConfigDB_FeatureModel_selected")); - assertEquals(2, result.get("ConfigDB_AssignmentsSample_selected")); + assertEquals(3, result.get("ConfigDB_AssignmentsSample_selected")); assertEquals(26, result.get("API_FeatureModel_selected")); - assertEquals(1, result.get("API_AssignmentsSample_selected")); + assertEquals(2, result.get("API_AssignmentsSample_selected")); assertEquals(26, result.get("OS_FeatureModel_selected")); assertEquals(0, result.get("OS_AssignmentsSample_selected")); assertEquals(14, result.get("Get_FeatureModel_selected")); - assertEquals(2, result.get("Get_AssignmentsSample_selected")); + assertEquals(3, result.get("Get_AssignmentsSample_selected")); assertEquals(16, result.get("Put_FeatureModel_selected")); - assertEquals(1, result.get("Put_AssignmentsSample_selected")); + assertEquals(2, result.get("Put_AssignmentsSample_selected")); assertEquals(16, result.get("Delete_FeatureModel_selected")); assertEquals(1, result.get("Delete_AssignmentsSample_selected")); assertEquals(13, result.get("Windows_FeatureModel_selected")); - assertEquals(1, result.get("Windows_AssignmentsSample_selected")); - assertEquals(13, result.get("Linux_FeatureModel_selected")); - assertEquals(1, result.get("Linux_AssignmentsSample_selected")); - assertEquals(1, result.get("Windows_AssignmentsSample_selected")); + assertEquals(2, result.get("Windows_AssignmentsSample_selected")); assertEquals(13, result.get("Linux_FeatureModel_selected")); assertEquals(1, result.get("Linux_AssignmentsSample_selected")); assertEquals(12, result.get("Transactions_FeatureModel_selected")); assertEquals(1, result.get("Transactions_AssignmentsSample_selected")); assertEquals(26, result.get("FeatureModel Valid")); - assertEquals(2, result.get("AssignmentsSample Valid")); + assertEquals(3, result.get("AssignmentsSample Valid")); + + assertEquals(0, result.get("ConfigDB_FeatureModel_undefined")); + assertEquals(0, result.get("ConfigDB_AssignmentsSample_undefined")); + assertEquals(1, result.get("API_AssignmentsSample_undefined")); + assertEquals(3, result.get("OS_AssignmentsSample_undefined")); + assertEquals(0, result.get("Get_AssignmentsSample_undefined")); + assertEquals(1, result.get("Put_AssignmentsSample_undefined")); + assertEquals(0, result.get("Delete_AssignmentsSample_undefined")); + assertEquals(0, result.get("Windows_AssignmentsSample_undefined")); + assertEquals(0, result.get("Linux_AssignmentsSample_undefined")); + assertEquals(0, result.get("Transactions_AssignmentsSample_undefined")); + + assertEquals(0, result.get("ConfigDB_FeatureModel_deselected")); + assertEquals(0, result.get("ConfigDB_AssignmentsSample_deselected")); + assertEquals(0, result.get("API_FeatureModel_deselected")); + assertEquals(0, result.get("API_AssignmentsSample_deselected")); + assertEquals(0, result.get("OS_FeatureModel_deselected")); + assertEquals(0, result.get("OS_AssignmentsSample_deselected")); + assertEquals(12, result.get("Get_FeatureModel_deselected")); + assertEquals(0, result.get("Get_AssignmentsSample_deselected")); + assertEquals(10, result.get("Put_FeatureModel_deselected")); + assertEquals(0, result.get("Put_AssignmentsSample_deselected")); + assertEquals(10, result.get("Delete_FeatureModel_deselected")); + assertEquals(2, result.get("Delete_AssignmentsSample_deselected")); + assertEquals(13, result.get("Windows_FeatureModel_deselected")); + assertEquals(1, result.get("Windows_AssignmentsSample_deselected")); + assertEquals(13, result.get("Linux_FeatureModel_deselected")); + assertEquals(2, result.get("Linux_AssignmentsSample_deselected")); + assertEquals(14, result.get("Transactions_FeatureModel_deselected")); + assertEquals(2, result.get("Transactions_AssignmentsSample_deselected")); computation.set(ComputeUniformity.ANALYSIS, true); result = computation.compute(); assertEquals(0, result.get("ConfigDB_selected")); - assertEquals(-(float) 1 / (float) 2, result.get("API_selected")); + assertEquals(((float) 2 / 3) - (float) 26 / (float) 26, result.get("API_selected")); assertEquals(-1, result.get("OS_selected")); - assertEquals(((float) 2 / 2) - ((float) 14 / 26), result.get("Get_selected")); - assertEquals(((float) 1 / 2) - ((float) 16 / 26), result.get("Put_selected")); - assertEquals(((float) 1 / 2) - ((float) 16 / 26), result.get("Delete_selected")); - assertEquals(0, result.get("Windows_selected")); - assertEquals(0, result.get("Linux_selected")); - assertEquals(((float) 1 / 2) - ((float) 12 / 26), result.get("Transactions_selected")); - System.out.println("Commonality difference per features: \n" + result); + assertEquals(((float) 3 / 3) - ((float) 14 / 26), result.get("Get_selected")); + assertEquals(((float) 2 / 3) - ((float) 16 / 26), result.get("Put_selected")); + assertEquals(((float) 1 / 3) - ((float) 16 / 26), result.get("Delete_selected")); + assertEquals(((float) 2 / 3) - ((float) 13 / 26), result.get("Windows_selected")); + assertEquals(((float) 1 / 3) - ((float) 13 / 26), result.get("Linux_selected")); + assertEquals(((float) 1 / 3) - ((float) 12 / 26), result.get("Transactions_selected")); + + assertEquals(0, result.get("ConfigDB_deselected")); + assertEquals(((float) 0 / 3) - (float) 0 / (float) 26, result.get("API_deselected")); + assertEquals(0, result.get("OS_deselected")); + assertEquals(((float) 0 / 3) - ((float) 12 / 26), result.get("Get_deselected")); + assertEquals(((float) 0 / 3) - ((float) 10 / 26), result.get("Put_deselected")); + assertEquals(((float) 2 / 3) - ((float) 10 / 26), result.get("Delete_deselected")); + assertEquals(((float) 1 / 3) - ((float) 13 / 26), result.get("Windows_deselected")); + assertEquals(((float) 2 / 3) - ((float) 13 / 26), result.get("Linux_deselected")); + assertEquals(((float) 2 / 3) - ((float)14 / 26), result.get("Transactions_deselected")); + + assertEquals(0, result.get("ConfigDB_undefined")); + assertEquals(((float) 1 / 3) - (float) 0 / (float) 26, result.get("API_undefined")); + assertEquals(1, result.get("OS_undefined")); + assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Get_undefined")); + assertEquals(((float) 1 / 3) - ((float) 0 / 26), result.get("Put_undefined")); + assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Delete_undefined")); + assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Windows_undefined")); + assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Linux_undefined")); + assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Transactions_undefined")); } private IFeatureTree generateMediumTree() { From 09304ee7f93856820e61082ed2f9f8e398c66516 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 11:21:36 +0200 Subject: [PATCH 202/257] test: added new tests for pdf export with directory creation --- .../VisualizeFeatureModelStatsTest.java | 58 ++++++++++++++++++- 1 file changed, 56 insertions(+), 2 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index 6a81d056..5aae7dcf 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -14,6 +14,9 @@ import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; import org.junit.jupiter.api.Test; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; @@ -111,23 +114,30 @@ public AnalysisTree getDoubleTree() { return analysisTreeFromFeatureModel(featureModel); } - // todo test for other polymorph display or export methods - @Test void twoPagePDFExport() { VisualizeGroupDistribution vizGroup; vizGroup = new VisualizeGroupDistribution(doubleTree); assertEquals(2, vizGroup.getCharts().size()); assertEquals(0, vizGroup.exportAllChartsToPDF(defaultExportName)); + assertTrue(Files.exists(Paths.get(defaultExportName))); } @Test void pdfValidIndexGroupDistribution() { VisualizeGroupDistribution vizGroup; + vizGroup = new VisualizeGroupDistribution(mediumTree); assertEquals(0, vizGroup.exportChartToPDF(0, defaultExportName)); + assertTrue(Files.exists(Paths.get(defaultExportName))); + vizGroup = new VisualizeGroupDistribution(bigTree); assertEquals(0, vizGroup.exportChartToPDF(0, defaultExportName)); + assertTrue(Files.exists(Paths.get(defaultExportName))); + + vizGroup = new VisualizeGroupDistribution(doubleTree); + assertEquals(0, vizGroup.exportChartToPDF(1, defaultExportName)); + assertTrue(Files.exists(Paths.get(defaultExportName))); } @Test @@ -135,10 +145,12 @@ void pdfValidIndexOperatorDistribution() { VisualizeConstraintOperatorDistribution vizOpDis; vizOpDis = new VisualizeConstraintOperatorDistribution(bigTree); assertEquals(0, vizOpDis.exportChartToPDF(0, defaultExportName)); + assertTrue(Files.exists(Paths.get(defaultExportName))); } @Test void pdfInvalidIndex() { + // todo question: is one test enough? VisualizeGroupDistribution vizGroup; vizGroup = new VisualizeGroupDistribution(mediumTree); assertEquals(1, vizGroup.exportChartToPDF(99, defaultExportName)); @@ -175,4 +187,46 @@ void changeChartWidth() { vizGroup.setWidth(chartWidth); assertEquals(chartWidth, vizGroup.getCharts().get(0).getWidth()); } + + @Test + void invalidPDFPath() { + VisualizeGroupDistribution vizGroup; + vizGroup = new VisualizeGroupDistribution(mediumTree); + assertEquals(1, vizGroup.exportChartToPDF("?/x.xml")); + } + + @Test + void pdfExportWithFolderCreation() throws IOException { + String[] allPaths = { + "export.pdf", + "Visualizer Test Folder/export.pdf", + "Visualizer Test Folder/Nested Folder/export.pdf" + }; + + //cleanup + for (String path: allPaths) { + Files.deleteIfExists(Paths.get(path)); + } + + // actual tests + VisualizeGroupDistribution vizGroup = new VisualizeGroupDistribution(mediumTree); + for (String path: allPaths) { + assertEquals(0, vizGroup.exportChartToPDF(path)); + Path castedPath = Paths.get(path); + assertTrue(Files.exists(castedPath)); + File file = castedPath.toFile(); + assertTrue(file.exists() && file.isFile()); + assertTrue(file.length() > 1); + + } + + // clean up + for (String path: allPaths) { + Path castedPasted = Paths.get(path); + Files.deleteIfExists(castedPasted); + } + Files.delete(Paths.get("Visualizer Test Folder/Nested Folder")); + Files.delete(Paths.get("Visualizer Test Folder")); + } + } From 55e0f341d16e50892a7ed2233baf7d0146f7cfad Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 11:23:20 +0200 Subject: [PATCH 203/257] feat: pdfexport now creates directory if it does not already exist. doc: added a few todo reminders for ourselves --- .../AVisualizeFeatureModelStats.java | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index d2ce46a7..e9fa81c1 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -17,6 +17,9 @@ import java.awt.*; import java.awt.geom.AffineTransform; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.util.*; import java.util.List; import java.util.stream.Collectors; @@ -50,26 +53,26 @@ public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { public ArrayList> getCharts() { return this.charts; } - + // Question - goog idea? public void setCharts(ArrayList> charts) { this.charts = charts; this.charts = buildCharts(); } - public Integer getWidth() { + public int getWidth() { return this.chartWidth; } - public void setWidth(Integer width) { + public void setWidth(int width) { this.chartWidth = width; this.charts = buildCharts(); } - public Integer getHeight() { + public int getHeight() { return this.chartHeight; } - public void setHeight(Integer height) { + public void setHeight(int height) { this.chartHeight = height; this.charts = buildCharts(); } @@ -116,7 +119,7 @@ public LinkedHashMap> extractAnalysisTree( // fetches keys for all trees for the data we want // example: [Tree 1] Group Distribution, [Tree 2] Group Distribution, ... List featureTreeDataKeys = analysisMap.keySet().stream() - .filter(key -> key.contains(this.getAnalysisTreeDataName())) + .filter(key -> key.contains(getAnalysisTreeDataName())) .sorted() .collect(Collectors.toList()); @@ -163,7 +166,7 @@ private HashMap extractAnalysisMap() { HashMap receivedResult = result.get(); assert receivedResult != null: "Analysis Tree Visitor failed to produce a result."; - // we currently trust that this is always "Analysis" + // TODO we currently trust that this is always "Analysis" @SuppressWarnings("unchecked") HashMap analysisMap = (HashMap) receivedResult.get("Analysis"); assert analysisMap != null: "Received no \"Analysis\" HashMap from AnalysisTree"; @@ -193,7 +196,8 @@ private HashMap extractAnalysisMap() { defaultStyler(chart); HashMap treeData = analysisTreeData.get(treeKey); - treeData.forEach((key, value) -> chart.addSeries(key, (Integer) value)); + // TODO Ist der Cast wirklich i.o? + treeData.forEach((key, value) -> chart.addSeries(key, (Number) value)); charts.add(chart); } @@ -206,8 +210,8 @@ private HashMap extractAnalysisMap() { */ protected ArrayList> buildBoxCharts() { ArrayList> charts = new ArrayList<>(); - // in averagenumberofchildren only one double is provided instead of an double array, so I used testdata to test die BoxPlot - // for example not the average number of children, but the number of children for every node as a double or int array is needed to plot a boxplot for the average number of children + // TODO in averagenumberofchildren only one double is provided instead of an double array, so I used testdata to test die BoxPlot + // TODO for example not the average number of children, but the number of children for every node as a double or int array is needed to plot a boxplot for the average number of children double[] testdata = {0.9, 1.5, 2.22}; for (String treeKey : this.analysisTreeData.keySet()) { @@ -219,10 +223,11 @@ private HashMap extractAnalysisMap() { .height(getHeight()) .build(); chart.setTitle(treeKey); + defaultStyler(chart); HashMap treeData = analysisTreeData.get(treeKey); - treeData.forEach((key, value) -> chart.addSeries(key, testdata)); + treeData.forEach((key, value) -> chart.addSeries(key, (double[]) testdata)); charts.add(chart); } @@ -396,6 +401,13 @@ public int exportAllChartsToPDF(List> charts, String path) { return 1; } } + + // create folder if it does not already exist + Path pathToFolder = Paths.get(path).getParent(); + if (pathToFolder != null && !Files.exists(pathToFolder)) { + Files.createDirectories(pathToFolder); + } + document.save(path); return 0; From 660712a827da193f3fd7f041f866ba54650a7988 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 11:26:53 +0200 Subject: [PATCH 204/257] chore: deleted personal test class --- .../model/analysis/visualization/Testtmp.java | 133 ------------------ 1 file changed, 133 deletions(-) delete mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java b/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java deleted file mode 100644 index 731037a8..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java +++ /dev/null @@ -1,133 +0,0 @@ -package de.featjar.feature.model.analysis.visualization; - -import de.featjar.base.data.Result; -import de.featjar.base.data.identifier.Identifiers; -import de.featjar.base.io.IO; -import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.IFeature; -import de.featjar.feature.model.IFeatureModel; -import de.featjar.feature.model.IFeatureTree; -import de.featjar.feature.model.analysis.AnalysisTree; -import de.featjar.feature.model.cli.PrintStatistics; -import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; -import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; - -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.LinkedHashMap; - -public class Testtmp { - - public static AnalysisTree createDefaultTree() { - AnalysisTree innereanalysisTree = new AnalysisTree<>( - "avgNumOfAtomsPerConstraints", - new AnalysisTree<>("test property", 3.3), - new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); - - AnalysisTree analysisTree = new AnalysisTree<>( - "Analysis", - new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), - new AnalysisTree<>("numOfTopFeatures", 3.3), - new AnalysisTree<>("treeDepth", 3), - new AnalysisTree<>("avgNumOfChildren", 3), - new AnalysisTree<>("numInOrGroups", 7), - new AnalysisTree<>("numInAltGroups", 5), - new AnalysisTree<>("numOfAtoms", 8), - new AnalysisTree<>("Group Distribution", 4), - innereanalysisTree); - return analysisTree; - } - - public static AnalysisTree generateEmptyTree() { - FeatureModel emptyFeatureModel = new FeatureModel(); - emptyFeatureModel.mutate().addFeatureTreeRoot(emptyFeatureModel.mutate().addFeature("root")); - - PrintStatistics printStatistics = new PrintStatistics(); - LinkedHashMap map = printStatistics.collectStats( - generateMediumTree(), - PrintStatistics.AnalysesScope.ALL - ); - - return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); - } - - /** - * Feature tree with three nodes under the root. API is mandatory and below it is an or-group with the features - * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. - * Transactions is an optional feature below the root. - * @return a medium-sized feature tree for testing purposes. - */ - public static FeatureModel generateMediumTree() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree treeRoot = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); - - IFeature featureAPI = featureModel.mutate().addFeature("API"); - IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); - treeAPI.isMandatory(); - IFeature featureGet = featureModel.mutate().addFeature("Get"); - treeAPI.mutate().addFeatureBelow(featureGet); - IFeature featurePut = featureModel.mutate().addFeature("Put"); - treeAPI.mutate().addFeatureBelow(featurePut); - IFeature featureDelete = featureModel.mutate().addFeature("Delete"); - treeAPI.mutate().addFeatureBelow(featureDelete); - treeAPI.mutate().toOrGroup(); - - IFeature featureOS = featureModel.mutate().addFeature("OS"); - IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); - treeOS.isMandatory(); - IFeature featureWindows = featureModel.mutate().addFeature("Windows"); - treeOS.mutate().addFeatureBelow(featureWindows); - IFeature featureLinux = featureModel.mutate().addFeature("Linux"); - treeOS.mutate().addFeatureBelow(featureLinux); - treeOS.mutate().toAlternativeGroup(); - - IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); - IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); - treeTransactions.isOptional(); - - return featureModel; - } - - public static void main(String[] args) { - PrintStatistics printStatistics = new PrintStatistics(); - LinkedHashMap map = printStatistics.collectStats( - generateMediumTree(), - PrintStatistics.AnalysesScope.ALL - ); - AnalysisTree mediumAnalysisTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); - - Path path = Paths.get("src/test/java/de/featjar/feature/model/visualization/model.xml"); - Result load = IO.load(path, new XMLFeatureModelFormat()); - FeatureModel model = (FeatureModel) load.orElseThrow(); - map = printStatistics.collectStats( - model, - PrintStatistics.AnalysesScope.ALL - ); - AnalysisTree bigAnalysisTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); - - /* - FeatureModel doubleTroubleModel = generateMediumTree(); - IFeatureTree clonedRoot = doubleTroubleModel.clone().getRoots().get(0); - doubleTroubleModel.mutate().addFeatureTreeRoot(clonedRoot); - map = printStatistics.collectStats( - doubleTroubleModel, - PrintStatistics.AnalysesScope.ALL - ); - AnalysisTree doubleTree = AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); - - */ - - - //VisualizeGroupDistribution viz = new VisualizeGroupDistribution(bigAnalysisTree); - //VisualizeConstraintOperatorDistribution viz2 = new VisualizeConstraintOperatorDistribution(bigAnalysisTree); - VisualizeAverageNumberOfChildren viz3 = new VisualizeAverageNumberOfChildren(bigAnalysisTree); - //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(createDefaultTree()); - //VisualizeFeatureGroupDistribution viz = new VisualizeFeatureGroupDistribution(generateEmptyTree()); - - //viz.displayChart(); - Integer retval = viz3.displayChart(); - System.out.println(retval); - - } -} From 575f04c2def64e856dfb4da7b588bfddf1506bbe Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 11:45:55 +0200 Subject: [PATCH 205/257] fix: extended catch scope of pdf export --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index e9fa81c1..b0762073 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -18,6 +18,7 @@ import java.awt.geom.AffineTransform; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.InvalidPathException; import java.nio.file.Path; import java.nio.file.Paths; import java.util.*; @@ -411,8 +412,7 @@ public int exportAllChartsToPDF(List> charts, String path) { document.save(path); return 0; - } catch (IOException e) { - + } catch (IOException | InvalidPathException e) { FeatJAR.log().error(e); return 1; From abf0002ee374ec8d7d94ff53862f2f2d38b2de42 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 12:03:16 +0200 Subject: [PATCH 206/257] doc: enhanced docs with links --- .../AVisualizeFeatureModelStats.java | 30 ++++++++++++------- 1 file changed, 19 insertions(+), 11 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index b0762073..11a737fa 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -26,8 +26,9 @@ import java.util.stream.Collectors; /** - * A class that builds a visualization using the XChart library. - * Data is read as an {@link AnalysisTree} and the chart is built by the buildChart() methods of each child class. + * Visualizes and exports feature model statistics. + * Data is read as an {@link AnalysisTree}. Each child specifies the information to be read from the tree via + * {@link #getAnalysisTreeDataName()}, as well as how to build a chart from it via the {@link #buildCharts()} method. * * @author Benjamin von Holt * @author Valentin Laubsch @@ -44,6 +45,13 @@ public abstract class AVisualizeFeatureModelStats { protected Font fontLabels = new Font(Font.SANS_SERIF, Font.BOLD, 20); protected Font fontLegend = new Font(Font.SANS_SERIF, Font.PLAIN, 20); + /** + * Visualizes and exports feature model statistics. + * Data is read as an {@link AnalysisTree}. Each child specifies the information to be read from the tree via + * {@link #getAnalysisTreeDataName()}, as well as how to build a chart from it via the {@link #buildCharts()} method. + * + * @param analysisTree {@link AnalysisTree} over the entire feature model. + */ public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { this.analysisTree = analysisTree; this.analysisTreeData = extractAnalysisTree(); @@ -176,14 +184,14 @@ private HashMap extractAnalysisMap() { } /** - * Use analysisTreeData to access the data relevant for building your chart. - * There are also premade builders that you may adopt. + * Uses {@link #analysisTreeData} to access the data relevant for building your chart. + * There are also premade builders that you may adopt, such as {@link #buildPieCharts()}. * @return list containing one chart per tree in the feature model */ abstract ArrayList> buildCharts(); /** - * Premade builder for pie charts that you can use when implementing buildCharts(). + * Premade builder for pie charts that you can use when implementing {@link #buildCharts()}. * @return list containing one chart per tree in the feature model */ protected ArrayList> buildPieCharts() { @@ -206,7 +214,7 @@ private HashMap extractAnalysisMap() { } /** - * Premade builder for box charts that you can use when implementing buildCharts(). + * Premade builder for box charts that you can use when implementing {@link #buildCharts()}. * @return list containing one chart per tree in the feature model */ protected ArrayList> buildBoxCharts() { @@ -247,7 +255,7 @@ private void defaultStyler(Chart chart) { } /** - * extends the defaultStyler() method with PieChart-specific calls + * extends {@link #defaultStyler(Chart)} with PieChart-specific calls */ private void defaultStylerPieChartExtension(PieChart chart) { chart.getStyler().setLabelsFont(fontLabels); @@ -298,7 +306,7 @@ public int displayChart (Integer index) { } /** - * Creates live preview pop-up windows of ALL internally generated charts. + * Creates live preview pop-up windows of ALL internally generated {@link #charts}. * @return 0 on success, 1 on general error, 2 on empty internal chart list */ public int displayAllCharts() { @@ -364,7 +372,7 @@ public int exportChartToPDF(Chart chart, String path) { * Creates a PDF document with a single page featuring the specified chart. * @param index index to retrieve a chart form the internal chart list * @param path full path to the destination file. Does not check whether you specified an extension. - * @return 0 on success, 1 on general errors, 2 if there are no internal charts to use. + * @return 0 on success, 1 on general errors, 2 if there are no internal {@link #charts} to use. */ public int exportChartToPDF(Integer index, String path) { if (chartsAreEmptyPDF()) {return 2;} @@ -379,7 +387,7 @@ public int exportChartToPDF(Integer index, String path) { /** * Creates a PDF document with a single page featuring the first chart from the internal list. * @param path full path to the destination file. Does not check whether you specified an extension. - * @return 0 on success, 1 on general errors, 2 if there are no internal charts to use. + * @return 0 on success, 1 on general errors, 2 if there are no internal {@link #charts} to use. */ public int exportChartToPDF(String path) { if (chartsAreEmptyPDF()) {return 2;} @@ -422,7 +430,7 @@ public int exportAllChartsToPDF(List> charts, String path) { /** * Creates a new PDF document and fills it with pages that each feature one chart from the list * @param path full path to the destination file. Does not check whether you specified an extension. - * @return 0 on success, 1 on general errors, 2 if there are no internal charts to use. + * @return 0 on success, 1 on general errors, 2 if there are no internal {@link #charts} to use. */ public int exportAllChartsToPDF(String path) { if (chartsAreEmptyPDF()) {return 2;} From 59ba1e4f4de0eb3ff8d831148c1f93a932c03f78 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 12:18:48 +0200 Subject: [PATCH 207/257] doc: added doc to both class and constructor, and updated child docs to be consistent. Left out child docs where they can be inherited. --- .../AVisualizeFeatureModelStats.java | 6 ++-- .../VisualizeAverageNumberOfChildren.java | 26 ++++++++------- ...sualizeConstraintOperatorDistribution.java | 32 +++++++++---------- .../VisualizeGroupDistribution.java | 29 +++++++++-------- 4 files changed, 48 insertions(+), 45 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 11a737fa..252539f8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -47,8 +47,6 @@ public abstract class AVisualizeFeatureModelStats { /** * Visualizes and exports feature model statistics. - * Data is read as an {@link AnalysisTree}. Each child specifies the information to be read from the tree via - * {@link #getAnalysisTreeDataName()}, as well as how to build a chart from it via the {@link #buildCharts()} method. * * @param analysisTree {@link AnalysisTree} over the entire feature model. */ @@ -111,7 +109,7 @@ private boolean chartsAreEmptyPDF() { } /** - * {@return String key used to fetch data from the Analysis Tree.} + * {@return String key used to fetch data from {@link #analysisTree}.} */ protected abstract String getAnalysisTreeDataName(); @@ -188,7 +186,7 @@ private HashMap extractAnalysisMap() { * There are also premade builders that you may adopt, such as {@link #buildPieCharts()}. * @return list containing one chart per tree in the feature model */ - abstract ArrayList> buildCharts(); + abstract protected ArrayList> buildCharts(); /** * Premade builder for pie charts that you can use when implementing {@link #buildCharts()}. diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java index b78e528c..c0f8e3b4 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java @@ -5,21 +5,25 @@ import java.util.ArrayList; +/** + * Visualizes and exports the feature model statistic "Average Number of Children". + * Data is read as an {@link AnalysisTree}. Each child specifies the information to be read from the tree via + * {@link #getAnalysisTreeDataName()}, as well as how to build a chart from it via the {@link #buildCharts()} method. + * + * @author Benjamin von Holt + * @author Valentin Laubsch + */ public class VisualizeAverageNumberOfChildren extends AVisualizeFeatureModelStats { - public VisualizeAverageNumberOfChildren(AnalysisTree analysisTree) { super(analysisTree); } - /** - * {@return String key used to fetch data from the Analysis Tree.} + * Visualizes and exports the feature model statistic "Average Number of Children". + * + * @param analysisTree {@link AnalysisTree} over the entire feature model. */ + public VisualizeAverageNumberOfChildren(AnalysisTree analysisTree) { super(analysisTree); } + @Override - protected String getAnalysisTreeDataName() { return "Average Number of Children"; } + protected String getAnalysisTreeDataName() {return "Average Number of Children";} - /** - * Use analysisTreeData to access the data relevant for building your chart. - * There are also premade builders that you may adopt. - * - * @return list containing one chart per tree in the feature model - */ @Override - ArrayList> buildCharts() { return buildBoxCharts(); } + protected ArrayList> buildCharts() {return buildBoxCharts();} } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java index 7b595fd3..5e89a972 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java @@ -5,27 +5,27 @@ import java.util.ArrayList; +/** + * Visualizes and exports the feature model statistic "Operator Distribution". + * Data is read as an {@link AnalysisTree}. Each child specifies the information to be read from the tree via + * {@link #getAnalysisTreeDataName()}, as well as how to build a chart from it via the {@link #buildCharts()} method. + * + * @author Benjamin von Holt + * @author Valentin Laubsch + */ public class VisualizeConstraintOperatorDistribution extends AVisualizeFeatureModelStats{ - public VisualizeConstraintOperatorDistribution(AnalysisTree analysisTree) { - super(analysisTree); - } /** - * {@return String key used to fetch data from the Analysis Tree.} + * Visualizes and exports the feature model statistic "Operator Distribution". + * + * @param analysisTree {@link AnalysisTree} over the entire feature model. */ + public VisualizeConstraintOperatorDistribution(AnalysisTree analysisTree) {super(analysisTree);} + @Override - protected String getAnalysisTreeDataName() { - return "Operator Distribution"; - } + protected String getAnalysisTreeDataName() {return "Operator Distribution";} - /** - * Use analysisTreeData to access the data relevant for building your chart. - * There are also premade builders that you may adopt. - * - * @return list containing one chart per tree in the feature model - */ @Override - ArrayList> buildCharts() { - return buildPieCharts(); - } + protected ArrayList> buildCharts() {return buildPieCharts();} + } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java index a0a5a051..26d0ac8f 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java @@ -5,25 +5,26 @@ import java.util.ArrayList; +/** + * Visualizes and exports the feature model statistic "Group Distribution". + * Data is read as an {@link AnalysisTree}. Each child specifies the information to be read from the tree via + * {@link #getAnalysisTreeDataName()}, as well as how to build a chart from it via the {@link #buildCharts()} method. + * + * @author Benjamin von Holt + * @author Valentin Laubsch + */ public class VisualizeGroupDistribution extends AVisualizeFeatureModelStats{ - public VisualizeGroupDistribution(AnalysisTree analysisTree) { - super(analysisTree); - } /** - * {@return String key used to fetch data from the Analysis Tree.} + * Visualizes and exports the feature model statistic "Operator Distribution". + * + * @param analysisTree {@link AnalysisTree} over the entire feature model. */ + public VisualizeGroupDistribution(AnalysisTree analysisTree) {super(analysisTree);} + @Override - protected String getAnalysisTreeDataName() { - return "Group Distribution"; - } + protected String getAnalysisTreeDataName() {return "Group Distribution";} - /** - * You can use the analysisTreeData array list to access the analysisTree data relevant for building your chart. - * @return the chart that will be used by the other class methods - */ @Override - ArrayList> buildCharts() { - return buildPieCharts(); - } + protected ArrayList> buildCharts() {return buildPieCharts();} } From 81adede3e174fa4e453ede4494c0b9c41ba6329a Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 12:22:16 +0200 Subject: [PATCH 208/257] doc: added missing doc for helper method --- .../analysis/visualization/VisualizeGroupDistribution.java | 2 +- .../model/visualization/VisualizeFeatureModelStatsTest.java | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java index 26d0ac8f..9f9fa004 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java @@ -17,7 +17,7 @@ public class VisualizeGroupDistribution extends AVisualizeFeatureModelStats{ /** * Visualizes and exports the feature model statistic "Operator Distribution". - * + * * @param analysisTree {@link AnalysisTree} over the entire feature model. */ public VisualizeGroupDistribution(AnalysisTree analysisTree) {super(analysisTree);} diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index 5aae7dcf..46069b15 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -82,6 +82,9 @@ public AnalysisTree analysisTreeFromFeatureModel(FeatureModel featureModel) { return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); } + /** + * Helper function. Converts an XML file into an {@link AnalysisTree} + */ public AnalysisTree analysisTreeFromXML (Path path) { Result load = IO.load(path, new XMLFeatureModelFormat()); FeatureModel model = (FeatureModel) load.orElseThrow(); From 509043ddadf244dea199eb618b43f258c75a2429 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 12:37:23 +0200 Subject: [PATCH 209/257] style: ran spotless apply for code style consistency --- .../AVisualizeFeatureModelStats.java | 122 +++++++++++------- .../VisualizeAverageNumberOfChildren.java | 35 ++++- ...sualizeConstraintOperatorDistribution.java | 38 +++++- .../VisualizeGroupDistribution.java | 37 +++++- .../feature/model/cli/PrintStatistics.java | 2 +- .../computation/ComputeAverageConstraint.java | 2 +- .../VisualizeFeatureModelStatsTest.java | 57 ++++---- 7 files changed, 202 insertions(+), 91 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 252539f8..039d518a 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.analysis.visualization; import de.featjar.base.FeatJAR; @@ -6,14 +26,6 @@ import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D; -import org.apache.pdfbox.pdmodel.PDDocument; -import org.apache.pdfbox.pdmodel.PDPage; -import org.apache.pdfbox.pdmodel.PDPageContentStream; -import org.apache.pdfbox.util.Matrix; -import org.knowm.xchart.*; -import org.knowm.xchart.internal.chartpart.Chart; - -import javax.swing.*; import java.awt.*; import java.awt.geom.AffineTransform; import java.io.IOException; @@ -24,6 +36,13 @@ import java.util.*; import java.util.List; import java.util.stream.Collectors; +import javax.swing.*; +import org.apache.pdfbox.pdmodel.PDDocument; +import org.apache.pdfbox.pdmodel.PDPage; +import org.apache.pdfbox.pdmodel.PDPageContentStream; +import org.apache.pdfbox.util.Matrix; +import org.knowm.xchart.*; +import org.knowm.xchart.internal.chartpart.Chart; /** * Visualizes and exports feature model statistics. @@ -35,12 +54,12 @@ */ public abstract class AVisualizeFeatureModelStats { - final protected AnalysisTree analysisTree; + protected final AnalysisTree analysisTree; protected LinkedHashMap> analysisTreeData; protected ArrayList> charts; - private Integer chartWidth = 800; - private Integer chartHeight = 600; + private int chartWidth = 800; + private int chartHeight = 600; protected Font fontTitle = new Font(Font.SANS_SERIF, Font.BOLD, 30); protected Font fontLabels = new Font(Font.SANS_SERIF, Font.BOLD, 20); protected Font fontLegend = new Font(Font.SANS_SERIF, Font.PLAIN, 20); @@ -98,7 +117,9 @@ protected boolean chartsAreEmpty(String extraMessage) { return false; } - private boolean chartsAreEmpty() {return chartsAreEmpty("");} + private boolean chartsAreEmpty() { + return chartsAreEmpty(""); + } private boolean chartsAreEmptyDisplay() { return chartsAreEmpty("Cannot display chart: "); @@ -143,15 +164,13 @@ public LinkedHashMap> extractAnalysisTree( @SuppressWarnings("unchecked") HashMap nestedMap = (HashMap) pieceOfInformation; - nestedMap.keySet().stream() - .sorted() - .forEach(nestedMapKey -> { - // the value relevant for us needs to be unpacked first - Object rawValue = nestedMap.get(nestedMapKey); - ArrayList castedValue = (ArrayList) rawValue; - Object value = castedValue.get(2); - featureTreeData.put(nestedMapKey, value); - }); + nestedMap.keySet().stream().sorted().forEach(nestedMapKey -> { + // the value relevant for us needs to be unpacked first + Object rawValue = nestedMap.get(nestedMapKey); + ArrayList castedValue = (ArrayList) rawValue; + Object value = castedValue.get(2); + featureTreeData.put(nestedMapKey, value); + }); } else if (pieceOfInformation instanceof ArrayList) { ArrayList castedValue = (ArrayList) pieceOfInformation; @@ -171,12 +190,12 @@ public LinkedHashMap> extractAnalysisTree( private HashMap extractAnalysisMap() { Result> result = Trees.traverse(analysisTree, new AnalysisTreeVisitor()); HashMap receivedResult = result.get(); - assert receivedResult != null: "Analysis Tree Visitor failed to produce a result."; + assert receivedResult != null : "Analysis Tree Visitor failed to produce a result."; // TODO we currently trust that this is always "Analysis" @SuppressWarnings("unchecked") HashMap analysisMap = (HashMap) receivedResult.get("Analysis"); - assert analysisMap != null: "Received no \"Analysis\" HashMap from AnalysisTree"; + assert analysisMap != null : "Received no \"Analysis\" HashMap from AnalysisTree"; return analysisMap; } @@ -186,7 +205,7 @@ private HashMap extractAnalysisMap() { * There are also premade builders that you may adopt, such as {@link #buildPieCharts()}. * @return list containing one chart per tree in the feature model */ - abstract protected ArrayList> buildCharts(); + protected abstract ArrayList> buildCharts(); /** * Premade builder for pie charts that you can use when implementing {@link #buildCharts()}. @@ -195,10 +214,8 @@ private HashMap extractAnalysisMap() { protected ArrayList> buildPieCharts() { ArrayList> charts = new ArrayList<>(); for (String treeKey : this.analysisTreeData.keySet()) { - PieChart chart = new PieChartBuilder() - .width(getWidth()) - .height(getHeight()) - .build(); + PieChart chart = + new PieChartBuilder().width(getWidth()).height(getHeight()).build(); chart.setTitle(treeKey); defaultStyler(chart); @@ -217,18 +234,18 @@ private HashMap extractAnalysisMap() { */ protected ArrayList> buildBoxCharts() { ArrayList> charts = new ArrayList<>(); - // TODO in averagenumberofchildren only one double is provided instead of an double array, so I used testdata to test die BoxPlot - // TODO for example not the average number of children, but the number of children for every node as a double or int array is needed to plot a boxplot for the average number of children + // TODO in averagenumberofchildren only one double is provided instead of an double array, so I used testdata to + // test die BoxPlot + // TODO for example not the average number of children, but the number of children for every node as a double or + // int array is needed to plot a boxplot for the average number of children double[] testdata = {0.9, 1.5, 2.22}; for (String treeKey : this.analysisTreeData.keySet()) { // Momentan bauen wir pro Tree einen Chart mit einer Box mit mehreren Werten // Wäre es für Boxplots nicht sinnvoller einen Chart für alle Trees zu machen, // mit je eine Box pro Tree? - BoxChart chart = new BoxChartBuilder() - .width(getWidth()) - .height(getHeight()) - .build(); + BoxChart chart = + new BoxChartBuilder().width(getWidth()).height(getHeight()).build(); chart.setTitle(treeKey); defaultStyler(chart); @@ -264,7 +281,7 @@ private void defaultStylerPieChartExtension(PieChart chart) { * @param chart the chart that will be displayed * @return 0 on success, 1 on general error */ - public int displayChart (Chart chart) { + public int displayChart(Chart chart) { try { JFrame jframe = new SwingWrapper<>(chart).displayChart(); if (jframe == null) { @@ -284,7 +301,9 @@ public int displayChart (Chart chart) { * @return 0 on success, 1 on general error, 2 on empty internal chart list */ public int displayChart() { - if (chartsAreEmptyDisplay()) {return 2;} + if (chartsAreEmptyDisplay()) { + return 2; + } return this.displayChart(0); } @@ -292,12 +311,14 @@ public int displayChart() { * Creates a live preview pop-up window of an internally generated chart, fetched by index. * @return 0 on success, 1 on general error, 2 on empty internal chart list */ - public int displayChart (Integer index) { - if (chartsAreEmptyDisplay()) {return 2;} + public int displayChart(int index) { + if (chartsAreEmptyDisplay()) { + return 2; + } try { this.displayChart(this.charts.get(index)); } catch (IndexOutOfBoundsException e) { - FeatJAR.log().error("Unable to fetch chart with index "+ index + ": " + e); + FeatJAR.log().error("Unable to fetch chart with index " + index + ": " + e); return 1; } return 0; @@ -308,7 +329,9 @@ public int displayChart (Integer index) { * @return 0 on success, 1 on general error, 2 on empty internal chart list */ public int displayAllCharts() { - if (chartsAreEmptyDisplay()) {return 2;} + if (chartsAreEmptyDisplay()) { + return 2; + } int returnValue = 0; @@ -372,12 +395,14 @@ public int exportChartToPDF(Chart chart, String path) { * @param path full path to the destination file. Does not check whether you specified an extension. * @return 0 on success, 1 on general errors, 2 if there are no internal {@link #charts} to use. */ - public int exportChartToPDF(Integer index, String path) { - if (chartsAreEmptyPDF()) {return 2;} + public int exportChartToPDF(int index, String path) { + if (chartsAreEmptyPDF()) { + return 2; + } try { return exportChartToPDF(charts.get(index), path); } catch (IndexOutOfBoundsException e) { - FeatJAR.log().error("Unable to fetch chart with index "+ index + ": " + e); + FeatJAR.log().error("Unable to fetch chart with index " + index + ": " + e); return 1; } } @@ -388,7 +413,9 @@ public int exportChartToPDF(Integer index, String path) { * @return 0 on success, 1 on general errors, 2 if there are no internal {@link #charts} to use. */ public int exportChartToPDF(String path) { - if (chartsAreEmptyPDF()) {return 2;} + if (chartsAreEmptyPDF()) { + return 2; + } return exportChartToPDF(0, path); } @@ -421,7 +448,6 @@ public int exportAllChartsToPDF(List> charts, String path) { } catch (IOException | InvalidPathException e) { FeatJAR.log().error(e); return 1; - } } @@ -431,7 +457,9 @@ public int exportAllChartsToPDF(List> charts, String path) { * @return 0 on success, 1 on general errors, 2 if there are no internal {@link #charts} to use. */ public int exportAllChartsToPDF(String path) { - if (chartsAreEmptyPDF()) {return 2;} + if (chartsAreEmptyPDF()) { + return 2; + } return exportAllChartsToPDF(charts, path); } @@ -459,6 +487,4 @@ private Matrix getPDFCenteringMatrix(PDPage page) { return new Matrix(at); } - - } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java index c0f8e3b4..a83b0dbf 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java @@ -1,9 +1,28 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.analysis.visualization; import de.featjar.feature.model.analysis.AnalysisTree; -import org.knowm.xchart.internal.chartpart.Chart; - import java.util.ArrayList; +import org.knowm.xchart.internal.chartpart.Chart; /** * Visualizes and exports the feature model statistic "Average Number of Children". @@ -19,11 +38,17 @@ public class VisualizeAverageNumberOfChildren extends AVisualizeFeatureModelStat * * @param analysisTree {@link AnalysisTree} over the entire feature model. */ - public VisualizeAverageNumberOfChildren(AnalysisTree analysisTree) { super(analysisTree); } + public VisualizeAverageNumberOfChildren(AnalysisTree analysisTree) { + super(analysisTree); + } @Override - protected String getAnalysisTreeDataName() {return "Average Number of Children";} + protected String getAnalysisTreeDataName() { + return "Average Number of Children"; + } @Override - protected ArrayList> buildCharts() {return buildBoxCharts();} + protected ArrayList> buildCharts() { + return buildBoxCharts(); + } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java index 5e89a972..06a30bdf 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeConstraintOperatorDistribution.java @@ -1,9 +1,28 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.analysis.visualization; import de.featjar.feature.model.analysis.AnalysisTree; -import org.knowm.xchart.internal.chartpart.Chart; - import java.util.ArrayList; +import org.knowm.xchart.internal.chartpart.Chart; /** * Visualizes and exports the feature model statistic "Operator Distribution". @@ -13,19 +32,24 @@ * @author Benjamin von Holt * @author Valentin Laubsch */ -public class VisualizeConstraintOperatorDistribution extends AVisualizeFeatureModelStats{ +public class VisualizeConstraintOperatorDistribution extends AVisualizeFeatureModelStats { /** * Visualizes and exports the feature model statistic "Operator Distribution". * * @param analysisTree {@link AnalysisTree} over the entire feature model. */ - public VisualizeConstraintOperatorDistribution(AnalysisTree analysisTree) {super(analysisTree);} + public VisualizeConstraintOperatorDistribution(AnalysisTree analysisTree) { + super(analysisTree); + } @Override - protected String getAnalysisTreeDataName() {return "Operator Distribution";} + protected String getAnalysisTreeDataName() { + return "Operator Distribution"; + } @Override - protected ArrayList> buildCharts() {return buildPieCharts();} - + protected ArrayList> buildCharts() { + return buildPieCharts(); + } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java index 9f9fa004..28396b60 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeGroupDistribution.java @@ -1,9 +1,28 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.analysis.visualization; import de.featjar.feature.model.analysis.AnalysisTree; -import org.knowm.xchart.internal.chartpart.Chart; - import java.util.ArrayList; +import org.knowm.xchart.internal.chartpart.Chart; /** * Visualizes and exports the feature model statistic "Group Distribution". @@ -13,18 +32,24 @@ * @author Benjamin von Holt * @author Valentin Laubsch */ -public class VisualizeGroupDistribution extends AVisualizeFeatureModelStats{ +public class VisualizeGroupDistribution extends AVisualizeFeatureModelStats { /** * Visualizes and exports the feature model statistic "Operator Distribution". * * @param analysisTree {@link AnalysisTree} over the entire feature model. */ - public VisualizeGroupDistribution(AnalysisTree analysisTree) {super(analysisTree);} + public VisualizeGroupDistribution(AnalysisTree analysisTree) { + super(analysisTree); + } @Override - protected String getAnalysisTreeDataName() {return "Group Distribution";} + protected String getAnalysisTreeDataName() { + return "Group Distribution"; + } @Override - protected ArrayList> buildCharts() {return buildPieCharts();} + protected ArrayList> buildCharts() { + return buildPieCharts(); + } } diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 20c25215..c7ef28b2 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -194,7 +194,7 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc HashMap computational_opDensity = Computations.of(model).map(ComputeOperatorDistribution::new).compute(); - //data.put("Operator Distribution", computational_opDensity); + // data.put("Operator Distribution", computational_opDensity); if (computational_opDensity.size() != 0) { data.put("Operator Distribution", computational_opDensity); } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 56a04ec4..aab83637 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -76,7 +76,7 @@ public Result compute(List dependencyList, Progress progress) { .orElse(0); } int numberOfConstraints = featureModel.getConstraints().size(); - float averageConstraint = (numberOfConstraints == 0)? 0 : (float) atomsSum / (float) numberOfConstraints; + float averageConstraint = (numberOfConstraints == 0) ? 0 : (float) atomsSum / (float) numberOfConstraints; return Result.of(averageConstraint); } } diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index 46069b15..650b240b 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -1,4 +1,27 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.visualization; + +import static org.junit.jupiter.api.Assertions.*; + import de.featjar.base.data.Result; import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.io.IO; @@ -12,16 +35,13 @@ import de.featjar.feature.model.cli.PrintStatistics; import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; -import org.junit.jupiter.api.Test; - import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; - -import static org.junit.jupiter.api.Assertions.*; +import org.junit.jupiter.api.Test; public class VisualizeFeatureModelStatsTest { AnalysisTree bigTree = getBigAnalysisTree(); @@ -75,29 +95,24 @@ public FeatureModel buildMediumFeatureModel() { */ public AnalysisTree analysisTreeFromFeatureModel(FeatureModel featureModel) { PrintStatistics printStatistics = new PrintStatistics(); - LinkedHashMap map = printStatistics.collectStats( - featureModel, - PrintStatistics.AnalysesScope.ALL - ); + LinkedHashMap map = + printStatistics.collectStats(featureModel, PrintStatistics.AnalysesScope.ALL); return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); } /** * Helper function. Converts an XML file into an {@link AnalysisTree} */ - public AnalysisTree analysisTreeFromXML (Path path) { + public AnalysisTree analysisTreeFromXML(Path path) { Result load = IO.load(path, new XMLFeatureModelFormat()); FeatureModel model = (FeatureModel) load.orElseThrow(); PrintStatistics printStatistics = new PrintStatistics(); - LinkedHashMap map = printStatistics.collectStats( - model, - PrintStatistics.AnalysesScope.ALL - ); + LinkedHashMap map = printStatistics.collectStats(model, PrintStatistics.AnalysesScope.ALL); return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); } - public AnalysisTree getBigAnalysisTree () { + public AnalysisTree getBigAnalysisTree() { return analysisTreeFromXML(Paths.get("src/test/java/de/featjar/feature/model/visualization/model.xml")); } @@ -201,35 +216,31 @@ void invalidPDFPath() { @Test void pdfExportWithFolderCreation() throws IOException { String[] allPaths = { - "export.pdf", - "Visualizer Test Folder/export.pdf", - "Visualizer Test Folder/Nested Folder/export.pdf" + "export.pdf", "Visualizer Test Folder/export.pdf", "Visualizer Test Folder/Nested Folder/export.pdf" }; - //cleanup - for (String path: allPaths) { + // cleanup + for (String path : allPaths) { Files.deleteIfExists(Paths.get(path)); } // actual tests VisualizeGroupDistribution vizGroup = new VisualizeGroupDistribution(mediumTree); - for (String path: allPaths) { + for (String path : allPaths) { assertEquals(0, vizGroup.exportChartToPDF(path)); Path castedPath = Paths.get(path); assertTrue(Files.exists(castedPath)); File file = castedPath.toFile(); assertTrue(file.exists() && file.isFile()); assertTrue(file.length() > 1); - } // clean up - for (String path: allPaths) { + for (String path : allPaths) { Path castedPasted = Paths.get(path); Files.deleteIfExists(castedPasted); } Files.delete(Paths.get("Visualizer Test Folder/Nested Folder")); Files.delete(Paths.get("Visualizer Test Folder")); } - } From bfb6a2f229cce2894b211322015c5e070b8c76f6 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 13:33:15 +0200 Subject: [PATCH 210/257] doc: added missing credits --- .../feature/model/analysis/visitor/FeatureTreeGroupCounter.java | 1 + .../feature/model/analysis/visitor/TreeAvgChildrenCounter.java | 1 + .../featjar/feature/model/analysis/visitor/TreeLeafCounter.java | 1 + 3 files changed, 3 insertions(+) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java index fcae6df4..268d3460 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java @@ -31,6 +31,7 @@ /** * Counts the share of groups found in the given feature tree, in order: AlternativeGroup, OrGroup, AndGroup, OtherGroup. * + * @author Valentin Laubsch * @author Benjamin von Holt */ public class FeatureTreeGroupCounter implements ITreeVisitor, HashMap> { diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java index a339c4c3..e76b60e5 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java @@ -29,6 +29,7 @@ * Calculates the average amount of children per node in the tree. * Returns 0 if tree has no nodes. * + * @author Valentin Laubsch * @author Benjamin von Holt */ public class TreeAvgChildrenCounter implements ITreeVisitor, Double> { diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java index d158de51..09940f01 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java @@ -29,6 +29,7 @@ * Counts the number of nodes that have no child nodes * Can be passed a class up to which should be counted (e.g., to exclude details in a tree). * + * @author Valentin Laubsch * @author Benjamin von Holt */ public class TreeLeafCounter implements ITreeVisitor, Integer> { From 6708bef46de0c2da0134644deddbcad4b705e4c0 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 13:33:47 +0200 Subject: [PATCH 211/257] feat: incorporated feedback: Chart title can be manually set, pie chart builder checks for valid input data type --- .../AVisualizeFeatureModelStats.java | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 039d518a..467cd3c8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -58,6 +58,7 @@ public abstract class AVisualizeFeatureModelStats { protected LinkedHashMap> analysisTreeData; protected ArrayList> charts; + private String chartTitle = null; private int chartWidth = 800; private int chartHeight = 600; protected Font fontTitle = new Font(Font.SANS_SERIF, Font.BOLD, 30); @@ -79,12 +80,21 @@ public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { public ArrayList> getCharts() { return this.charts; } - // Question - goog idea? + public void setCharts(ArrayList> charts) { this.charts = charts; this.charts = buildCharts(); } + public String getChartTitle() { + return chartTitle; + } + + public void setChartTitle(String chartTitle) { + this.chartTitle = chartTitle; + this.charts = buildCharts(); + } + public int getWidth() { return this.chartWidth; } @@ -216,12 +226,17 @@ private HashMap extractAnalysisMap() { for (String treeKey : this.analysisTreeData.keySet()) { PieChart chart = new PieChartBuilder().width(getWidth()).height(getHeight()).build(); - chart.setTitle(treeKey); + chart.setTitle((this.chartTitle == null) ? treeKey : this.chartTitle); defaultStyler(chart); HashMap treeData = analysisTreeData.get(treeKey); - // TODO Ist der Cast wirklich i.o? - treeData.forEach((key, value) -> chart.addSeries(key, (Number) value)); + + try { + treeData.forEach((key, value) -> chart.addSeries(key, (Number) value)); + } catch (ClassCastException e) { + FeatJAR.log().error("Invalid data for pie chart: " + e); + return new ArrayList<>(); + } charts.add(chart); } From b4823c1aa574ec53ccaf9004138d99c6c6311bb7 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Thu, 16 Oct 2025 13:49:18 +0200 Subject: [PATCH 212/257] feat: added cli for sampleproperties. tests: added tests for cli including model and configurations. refactor: changed computation dependency visibility --- .../ComputeDistributionFeatureSelections.java | 8 +- .../model/analysis/ComputeUniformity.java | 2 +- .../model/cli/PrintSampleStatistics.java | 202 ++++++++++++++++++ src/main/resources/extensions.xml | 1 + .../model/analysis/SamplePropertiesTest.java | 2 +- .../model/cli/PrintSampleStatisticsTest.java | 182 ++++++++++++++++ .../model/cli/resources/DB_FeatureModel.xml | 27 +++ .../model/cli/resources/DB_configs.csv | 7 + 8 files changed, 425 insertions(+), 6 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java create mode 100644 src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/DB_FeatureModel.xml create mode 100644 src/test/java/de/featjar/feature/model/cli/resources/DB_configs.csv diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java b/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java index 1e0f4184..9986094c 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java @@ -27,7 +27,7 @@ import de.featjar.base.data.Result; import de.featjar.formula.assignment.BooleanAssignment; import de.featjar.formula.assignment.BooleanAssignmentList; -import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; /** @@ -36,7 +36,7 @@ * @author Mohammad Khair Almekkawi * @author Florian Beese */ -public class ComputeDistributionFeatureSelections extends AComputation> { +public class ComputeDistributionFeatureSelections extends AComputation> { protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); @@ -46,9 +46,9 @@ public ComputeDistributionFeatureSelections(IComputation } @Override - public Result> compute(List dependencyList, Progress progress) { + public Result> compute(List dependencyList, Progress progress) { BooleanAssignmentList booleanAssigmenAssignmentList = BOOLEAN_ASSIGNMENT_LIST.get(dependencyList); - HashMap selectionDistribution = new HashMap(); + LinkedHashMap selectionDistribution = new LinkedHashMap(); selectionDistribution.put("selected", 0); selectionDistribution.put("deselected", 0); selectionDistribution.put("undefined", 0); diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java index 4e7349a8..b83a81d8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -58,7 +58,7 @@ */ public class ComputeUniformity extends AComputation> { - protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = + public static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); protected static final Dependency FEATURE_MODEL = Dependency.newDependency(IFeatureModel.class); protected static final Dependency ANALYSIS = Dependency.newDependency(Boolean.class); diff --git a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java new file mode 100644 index 00000000..edfd4c2b --- /dev/null +++ b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java @@ -0,0 +1,202 @@ +package de.featjar.feature.model.cli; + +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; + +import de.featjar.base.FeatJAR; +import de.featjar.base.cli.ACommand; +import de.featjar.base.cli.Option; +import de.featjar.base.cli.OptionList; +import de.featjar.base.computation.Computations; +import de.featjar.base.data.Result; +import de.featjar.base.io.IO; +import de.featjar.feature.model.IFeatureModel; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.ComputeDistributionFeatureSelections; +import de.featjar.feature.model.analysis.ComputeFeatureCounter; +import de.featjar.feature.model.analysis.ComputeNumberConfigurations; +import de.featjar.feature.model.analysis.ComputeNumberVariables; +import de.featjar.feature.model.analysis.ComputeUniformity; +import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.feature.model.io.csv.CSVAnalysisFormat; +import de.featjar.feature.model.io.json.JSONAnalysisFormat; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; +import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; +import de.featjar.formula.assignment.BooleanAssignmentList; +import de.featjar.formula.io.BooleanAssignmentListFormats; + +/** + * Prints statistics about given configuration(s) + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ +public class PrintSampleStatistics extends ACommand { + + private int exitCode = 0; + + public static final Option INPUT_OPTION_FM = Option.newOption("inputFM", Option.PathParser) + .setDescription("Path to input file containing feature model") + .setValidator(Option.PathValidator); + + public static final Option INPUT_OPTION_CONFIGS = Option.newOption("inputConfig", Option.PathParser) + .setDescription("Path to input file containing set of configuration(s)") + .setValidator(Option.PathValidator); + + public static final Option PRETTY_PRINT = + Option.newFlag("pretty").setDescription("Pretty prints the numbers"); + + @Override + public int run(OptionList optionParser) { + if (!optionParser.getResult(INPUT_OPTION_CONFIGS).isPresent()) { + FeatJAR.log().error("No Input file containing config(s) attached"); + return 1; + } + + if (!optionParser.getResult(INPUT_OPTION_FM).isPresent()) { + FeatJAR.log().error("No Input file containing feature model attached"); + return 1; + } + + Path pathConfig = optionParser.getResult(INPUT_OPTION_CONFIGS).orElseThrow(); + Result loadedConfig = IO.load(pathConfig, BooleanAssignmentListFormats.getInstance()); + Path pathFM = optionParser.getResult(INPUT_OPTION_FM).orElseThrow(); + Result loadedFM = IO.load(pathFM, FeatureModelFormats.getInstance()); + + LinkedHashMap data; + IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); + BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); + + data = collectStats(booleanAssignmentList, model); + + // if output path is specified, write statistics to file + if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { + writeTo(optionParser.getResult(OUTPUT_OPTION).get(), data); + } + + // printing statistics to console if no output file is specified + if (optionParser.get(PRETTY_PRINT)) { + printStatsPretty(data); + } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { + printStats(data); + } + + return exitCode; + } + + /** + * writes statistics into a file, depending on file type + * @param path: full path to output file + * @param type: is extracted from provided output path, needs to be lower case + */ + private void writeTo(Path path, LinkedHashMap data) { + + String type = IO.getFileExtension(path); + Result> tree = AnalysisTreeTransformer.hashMapToTree(data, type); + + try { + switch (type) { + case "csv": + IO.save(tree.get(), path, new CSVAnalysisFormat()); + break; + case "yaml": + IO.save(tree.get(), path, new YAMLAnalysisFormat()); + break; + case "json": + IO.save(tree.get(), path, new JSONAnalysisFormat()); + break; + case "": + FeatJAR.log().error("Output file does not include file type."); + exitCode = 1; + break; + default: + FeatJAR.log().error("File type not valid: " + type); + exitCode = 1; + } + } catch (Exception e) { + FeatJAR.log().error(e); + } + } + + /** + * Gathers statistics about given configuration set + * + * @param boolList - configurations as BooleanAssignmentList + * @param model - corresponding feature model + * @return returns Map containing statistics + */ + public LinkedHashMap collectStats(BooleanAssignmentList boolList, IFeatureModel model) { + + LinkedHashMap data = new LinkedHashMap<>(); + data.put("Number of Configurations", + Computations.of(boolList).map(ComputeNumberConfigurations::new).compute()); + data.put("Number of Variables", + Computations.of(boolList).map(ComputeNumberVariables::new).compute()); + data.put("Distribution of feature selection", + Computations.of(boolList).map(ComputeDistributionFeatureSelections::new).compute()); + data.put("Feature counter", + Computations.of(boolList).map(ComputeFeatureCounter::new).compute()); + data.put("Uniformity", + Computations.of(model).map(ComputeUniformity::new).set(ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, boolList).compute()); + return data; + } + /** + * prints data in a pretty way + * + * @param data - data to be printed + */ + public void printStatsPretty(LinkedHashMap data) { + FeatJAR.log().message("STATISTICS ABOUT GIVEN SAMPLE:\n" + buildStringPrettyStats(data)); + } + + /** + * + * @param data Map of the gathered statistics. Keys name the respective stat, values save the stat value itself. + * {@return StringBuilder with stats written into it in a pretty way} + */ + public StringBuilder buildStringPrettyStats(LinkedHashMap data) { + StringBuilder outputString = new StringBuilder(); + outputString.append(String.format("\n %-40s %n", "CONSTRAINT RELATED STATS\n")); + for (Map.Entry entry : data.entrySet()) { + if (entry.getValue() instanceof Map) { + Map nestedMap = (Map) entry.getValue(); + outputString.append(String.format("%-40s%n", entry.getKey())); + for (Map.Entry nestedEntry : nestedMap.entrySet()) { + outputString.append(String.format( + "%-40s : %s%n", " " + nestedEntry.getKey(), nestedEntry.getValue())); + } + } else { + outputString.append(String.format("%-40s : %s%n", entry.getKey(), entry.getValue())); + } + } + return outputString; + } + + /** + * Prints gathered statistics in a compact format. + * @param data: the previously computed data packaged line by line: String names the stat, Object holds the data. + */ + public void printStats(LinkedHashMap data) { + FeatJAR.log().message("STATISTICS ABOUT GIVEN SAMPLE:\n" + data); + } + + /** + * + * {@return brief description of this class} + */ + @Override + public Optional getDescription() { + return Optional.of("Prints out statistics about a given sample set."); + } + + /** + * + * {@return short name of this class} + */ + @Override + public Optional getShortName() { + return Optional.of("printSampleStats"); + } +} diff --git a/src/main/resources/extensions.xml b/src/main/resources/extensions.xml index f5cef2cd..c57ad1c4 100644 --- a/src/main/resources/extensions.xml +++ b/src/main/resources/extensions.xml @@ -6,6 +6,7 @@ + diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index bf51f013..71a5e959 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -137,7 +137,7 @@ public FeatureModel createMediumFeatureModel() { @Test public void computeDistributionFeaturesSelectionsTest() { BooleanAssignmentList booleanAssignmentList = createAssignmentList(); - IComputation> computational = + IComputation> computational = Computations.of(booleanAssignmentList).map(ComputeDistributionFeatureSelections::new); HashMap selectionDistribution = computational.compute(); assertEquals(7, selectionDistribution.get("selected")); diff --git a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java new file mode 100644 index 00000000..c39062f3 --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java @@ -0,0 +1,182 @@ +package de.featjar.feature.model.cli; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.LinkedHashMap; + +import org.junit.jupiter.api.Test; + +import de.featjar.base.FeatJAR; +import de.featjar.base.data.Result; +import de.featjar.base.io.IO; +import de.featjar.feature.model.IFeatureModel; +import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.formula.assignment.BooleanAssignmentList; +import de.featjar.formula.io.BooleanAssignmentListFormats; + +public class PrintSampleStatisticsTest { + private final String fmPath = "src/test/java/de/featjar/feature/model/cli/resources/DB_FeatureModel.xml"; + private final String configPath = "src/test/java/de/featjar/feature/model/cli/resources/DB_configs.csv"; + PrintSampleStatistics printSampleStats = new PrintSampleStatistics(); + + @Test + void inputTest() { + + int exit_code = FeatJAR.runTest( + "printSampleStats", "--inputFM", fmPath,"--inputConfig", configPath); + assertEquals(0, exit_code); + } + + @Test + void noInput() { + + assertEquals(1, FeatJAR.runTest("printSampleStats", "--inputFM")); + assertEquals(1, FeatJAR.runTest("printSampleStats")); + } + + @Test + void outputWithFileValidExtension() throws IOException { + + int exit_code = FeatJAR.runTest( + "printSampleStats", + "--inputFM", + fmPath, + "--inputConfig", + configPath, + "--output", + "model.csv"); + assertEquals(0, exit_code); + assertTrue(Files.exists(Paths.get("model.csv")) && !Files.isDirectory(Paths.get("model.csv"))); + Files.deleteIfExists(Paths.get("model.csv")); + } + + @Test + void outputWithFileInvalidExtension() { + + int exit_code = FeatJAR.runTest( + "printSampleStats", + "--inputFM", + fmPath, + "--inputConfig", + configPath, + "--output", + "model.pdf"); + assertEquals(1, exit_code); + } + + @Test + void outputWithoutFileExtension() { + + int exit_code = FeatJAR.runTest( + "printSampleStats", + "--inputFM", + fmPath, + "--inputConfig", + configPath, + "--output", + "desktop/folder"); + assertEquals(1, exit_code); + } + + @Test + void printComparison() { + FeatJAR.initialize(); + Result loadedConfig = IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); + Result loadedFM = IO.load(Paths.get(fmPath), FeatureModelFormats.getInstance()); + + IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); + BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); + + String content = "{Number of Configurations=6, Number of Variables=9, Distribution of feature selection={selected=24, deselected=20, undefined=10}, Feature counter={ConfigDB_selected=6, ConfigDB_deselected=0, ConfigDB_undefined=0, API_selected=2, API_deselected=1, API_undefined=3, OS_selected=0, OS_deselected=0, OS_undefined=6, Get_selected=6, Get_deselected=0, Get_undefined=0, Put_selected=2, Put_deselected=3, Put_undefined=1, Delete_selected=1, Delete_deselected=5, Delete_undefined=0, Windows_selected=3, Windows_deselected=3, Windows_undefined=0, Linux_selected=2, Linux_deselected=4, Linux_undefined=0, Transactions_selected=2, Transactions_deselected=4, Transactions_undefined=0}, Uniformity={ConfigDB_selected=0.0, ConfigDB_deselected=0.0, ConfigDB_undefined=0.0, API_selected=-0.3333333, API_deselected=0.0, API_undefined=0.33333334, OS_selected=-1.0, OS_deselected=0.0, OS_undefined=1.0, Get_selected=0.46153843, Get_deselected=-0.46153846, Get_undefined=0.0, Put_selected=0.05128205, Put_deselected=-0.3846154, Put_undefined=0.33333334, Delete_selected=-0.2820513, Delete_deselected=0.2820513, Delete_undefined=0.0, Windows_selected=0.16666669, Windows_deselected=-0.16666666, Windows_undefined=0.0, Linux_selected=-0.16666666, Linux_deselected=0.16666669, Linux_undefined=0.0, Transactions_selected=-0.12820512, Transactions_deselected=0.12820512, Transactions_undefined=0.0}}"; + String comparison = + printSampleStats.collectStats(booleanAssignmentList, model).toString(); + assertEquals(content, comparison); + FeatJAR.deinitialize(); + } + + @Test + void prettyStringBuilder() { + + FeatJAR.initialize(); + Result loadedConfig = IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); + Result loadedFM = IO.load(Paths.get(fmPath), FeatureModelFormats.getInstance()); + + IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); + BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); + + String comparison = + "\n CONSTRAINT RELATED STATS\n" + + " \n" + + "Number of Configurations : 6\n" + + "Number of Variables : 9\n" + + "Distribution of feature selection \n" + + " selected : 24\n" + + " deselected : 20\n" + + " undefined : 10\n" + + "Feature counter \n" + + " ConfigDB_selected : 6\n" + + " ConfigDB_deselected : 0\n" + + " ConfigDB_undefined : 0\n" + + " API_selected : 2\n" + + " API_deselected : 1\n" + + " API_undefined : 3\n" + + " OS_selected : 0\n" + + " OS_deselected : 0\n" + + " OS_undefined : 6\n" + + " Get_selected : 6\n" + + " Get_deselected : 0\n" + + " Get_undefined : 0\n" + + " Put_selected : 2\n" + + " Put_deselected : 3\n" + + " Put_undefined : 1\n" + + " Delete_selected : 1\n" + + " Delete_deselected : 5\n" + + " Delete_undefined : 0\n" + + " Windows_selected : 3\n" + + " Windows_deselected : 3\n" + + " Windows_undefined : 0\n" + + " Linux_selected : 2\n" + + " Linux_deselected : 4\n" + + " Linux_undefined : 0\n" + + " Transactions_selected : 2\n" + + " Transactions_deselected : 4\n" + + " Transactions_undefined : 0\n" + + "Uniformity \n" + + " ConfigDB_selected : 0.0\n" + + " ConfigDB_deselected : 0.0\n" + + " ConfigDB_undefined : 0.0\n" + + " API_selected : -0.3333333\n" + + " API_deselected : 0.0\n" + + " API_undefined : 0.33333334\n" + + " OS_selected : -1.0\n" + + " OS_deselected : 0.0\n" + + " OS_undefined : 1.0\n" + + " Get_selected : 0.46153843\n" + + " Get_deselected : -0.46153846\n" + + " Get_undefined : 0.0\n" + + " Put_selected : 0.05128205\n" + + " Put_deselected : -0.3846154\n" + + " Put_undefined : 0.33333334\n" + + " Delete_selected : -0.2820513\n" + + " Delete_deselected : 0.2820513\n" + + " Delete_undefined : 0.0\n" + + " Windows_selected : 0.16666669\n" + + " Windows_deselected : -0.16666666\n" + + " Windows_undefined : 0.0\n" + + " Linux_selected : -0.16666666\n" + + " Linux_deselected : 0.16666669\n" + + " Linux_undefined : 0.0\n" + + " Transactions_selected : -0.12820512\n" + + " Transactions_deselected : 0.12820512\n" + + " Transactions_undefined : 0.0\n" + + ""; + + LinkedHashMap map = printSampleStats.collectStats(booleanAssignmentList, model); + assertEquals(comparison, printSampleStats.buildStringPrettyStats(map).toString()); + FeatJAR.deinitialize(); + } +} diff --git a/src/test/java/de/featjar/feature/model/cli/resources/DB_FeatureModel.xml b/src/test/java/de/featjar/feature/model/cli/resources/DB_FeatureModel.xml new file mode 100644 index 00000000..e6cb55db --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/resources/DB_FeatureModel.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + Transactions + + Put + Delete + + + + + diff --git a/src/test/java/de/featjar/feature/model/cli/resources/DB_configs.csv b/src/test/java/de/featjar/feature/model/cli/resources/DB_configs.csv new file mode 100644 index 00000000..c4eeddcf --- /dev/null +++ b/src/test/java/de/featjar/feature/model/cli/resources/DB_configs.csv @@ -0,0 +1,7 @@ +Configuration;ConfigDB;API;OS;Get;Put;Delete;Windows;Linux;Transactions +0;+;+;0;+;0;-;+;-;- +1;+;0;0;+;+;+;-;+;+ +2;+;0;0;+;-;-;-;-;- +3;+;0;0;+;-;-;-;+;+ +4;+;-;0;+;-;-;+;-;- +5;+;+;0;+;+;-;+;-;- From 06f4e7430e59adfc231330b0bc6fbf78cf037cab Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 13:51:26 +0200 Subject: [PATCH 213/257] feat: pie chart builder can catch invalid negative input numbers --- .../visualization/AVisualizeFeatureModelStats.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 467cd3c8..b1e2fda8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -235,9 +235,16 @@ private HashMap extractAnalysisMap() { treeData.forEach((key, value) -> chart.addSeries(key, (Number) value)); } catch (ClassCastException e) { FeatJAR.log().error("Invalid data for pie chart: " + e); - return new ArrayList<>(); + continue; } + boolean anyNegativeValues = treeData.values().stream() + .map(value -> (Double) value) + .anyMatch(value -> value < 0); + if (anyNegativeValues) { + FeatJAR.log().error("Pie chart cannot be built with negative values"); + continue; + } charts.add(chart); } return charts; From c4aa5a07077174f5cd760d08d96058be4b01ddd7 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Thu, 16 Oct 2025 13:53:41 +0200 Subject: [PATCH 214/257] style: code formatting according to spotless and licensing --- .../model/cli/PrintSampleStatistics.java | 227 +++++++++-------- .../model/analysis/SamplePropertiesTest.java | 10 +- .../model/cli/PrintSampleStatisticsTest.java | 231 +++++++++--------- 3 files changed, 249 insertions(+), 219 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java index edfd4c2b..1334f4e9 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java @@ -1,10 +1,25 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.cli; -import java.nio.file.Path; -import java.util.LinkedHashMap; -import java.util.Map; -import java.util.Optional; - import de.featjar.base.FeatJAR; import de.featjar.base.cli.ACommand; import de.featjar.base.cli.Option; @@ -26,52 +41,56 @@ import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; import de.featjar.formula.assignment.BooleanAssignmentList; import de.featjar.formula.io.BooleanAssignmentListFormats; +import java.nio.file.Path; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.Optional; /** * Prints statistics about given configuration(s) - * + * * @author Mohammad Khair Almekkawi * @author Florian Beese */ public class PrintSampleStatistics extends ACommand { - - private int exitCode = 0; - - public static final Option INPUT_OPTION_FM = Option.newOption("inputFM", Option.PathParser) + + private int exitCode = 0; + + public static final Option INPUT_OPTION_FM = Option.newOption("inputFM", Option.PathParser) .setDescription("Path to input file containing feature model") .setValidator(Option.PathValidator); - - public static final Option INPUT_OPTION_CONFIGS = Option.newOption("inputConfig", Option.PathParser) + + public static final Option INPUT_OPTION_CONFIGS = Option.newOption("inputConfig", Option.PathParser) .setDescription("Path to input file containing set of configuration(s)") .setValidator(Option.PathValidator); public static final Option PRETTY_PRINT = Option.newFlag("pretty").setDescription("Pretty prints the numbers"); - @Override - public int run(OptionList optionParser) { - if (!optionParser.getResult(INPUT_OPTION_CONFIGS).isPresent()) { + @Override + public int run(OptionList optionParser) { + if (!optionParser.getResult(INPUT_OPTION_CONFIGS).isPresent()) { FeatJAR.log().error("No Input file containing config(s) attached"); return 1; } - - if (!optionParser.getResult(INPUT_OPTION_FM).isPresent()) { + + if (!optionParser.getResult(INPUT_OPTION_FM).isPresent()) { FeatJAR.log().error("No Input file containing feature model attached"); return 1; } - - Path pathConfig = optionParser.getResult(INPUT_OPTION_CONFIGS).orElseThrow(); + + Path pathConfig = optionParser.getResult(INPUT_OPTION_CONFIGS).orElseThrow(); Result loadedConfig = IO.load(pathConfig, BooleanAssignmentListFormats.getInstance()); - Path pathFM = optionParser.getResult(INPUT_OPTION_FM).orElseThrow(); + Path pathFM = optionParser.getResult(INPUT_OPTION_FM).orElseThrow(); Result loadedFM = IO.load(pathFM, FeatureModelFormats.getInstance()); - - LinkedHashMap data; - IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); - BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); - - data = collectStats(booleanAssignmentList, model); - - // if output path is specified, write statistics to file + + LinkedHashMap data; + IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); + BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); + + data = collectStats(booleanAssignmentList, model); + + // if output path is specified, write statistics to file if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { writeTo(optionParser.getResult(OUTPUT_OPTION).get(), data); } @@ -82,72 +101,82 @@ public int run(OptionList optionParser) { } else if (!optionParser.getResult(OUTPUT_OPTION).isPresent()) { printStats(data); } - - return exitCode; - } - - /** + + return exitCode; + } + + /** * writes statistics into a file, depending on file type * @param path: full path to output file * @param type: is extracted from provided output path, needs to be lower case */ private void writeTo(Path path, LinkedHashMap data) { - String type = IO.getFileExtension(path); - Result> tree = AnalysisTreeTransformer.hashMapToTree(data, type); - - try { - switch (type) { - case "csv": - IO.save(tree.get(), path, new CSVAnalysisFormat()); - break; - case "yaml": - IO.save(tree.get(), path, new YAMLAnalysisFormat()); - break; - case "json": - IO.save(tree.get(), path, new JSONAnalysisFormat()); - break; - case "": - FeatJAR.log().error("Output file does not include file type."); - exitCode = 1; - break; - default: - FeatJAR.log().error("File type not valid: " + type); - exitCode = 1; - } - } catch (Exception e) { - FeatJAR.log().error(e); - } + String type = IO.getFileExtension(path); + Result> tree = AnalysisTreeTransformer.hashMapToTree(data, type); + + try { + switch (type) { + case "csv": + IO.save(tree.get(), path, new CSVAnalysisFormat()); + break; + case "yaml": + IO.save(tree.get(), path, new YAMLAnalysisFormat()); + break; + case "json": + IO.save(tree.get(), path, new JSONAnalysisFormat()); + break; + case "": + FeatJAR.log().error("Output file does not include file type."); + exitCode = 1; + break; + default: + FeatJAR.log().error("File type not valid: " + type); + exitCode = 1; + } + } catch (Exception e) { + FeatJAR.log().error(e); + } } - + /** * Gathers statistics about given configuration set - * + * * @param boolList - configurations as BooleanAssignmentList * @param model - corresponding feature model * @return returns Map containing statistics */ - public LinkedHashMap collectStats(BooleanAssignmentList boolList, IFeatureModel model) { + public LinkedHashMap collectStats(BooleanAssignmentList boolList, IFeatureModel model) { LinkedHashMap data = new LinkedHashMap<>(); - data.put("Number of Configurations", - Computations.of(boolList).map(ComputeNumberConfigurations::new).compute()); - data.put("Number of Variables", - Computations.of(boolList).map(ComputeNumberVariables::new).compute()); - data.put("Distribution of feature selection", - Computations.of(boolList).map(ComputeDistributionFeatureSelections::new).compute()); - data.put("Feature counter", - Computations.of(boolList).map(ComputeFeatureCounter::new).compute()); - data.put("Uniformity", - Computations.of(model).map(ComputeUniformity::new).set(ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, boolList).compute()); + data.put( + "Number of Configurations", + Computations.of(boolList).map(ComputeNumberConfigurations::new).compute()); + data.put( + "Number of Variables", + Computations.of(boolList).map(ComputeNumberVariables::new).compute()); + data.put( + "Distribution of feature selection", + Computations.of(boolList) + .map(ComputeDistributionFeatureSelections::new) + .compute()); + data.put( + "Feature counter", + Computations.of(boolList).map(ComputeFeatureCounter::new).compute()); + data.put( + "Uniformity", + Computations.of(model) + .map(ComputeUniformity::new) + .set(ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, boolList) + .compute()); return data; - } - /** - * prints data in a pretty way - * - * @param data - data to be printed - */ - public void printStatsPretty(LinkedHashMap data) { + } + /** + * prints data in a pretty way + * + * @param data - data to be printed + */ + public void printStatsPretty(LinkedHashMap data) { FeatJAR.log().message("STATISTICS ABOUT GIVEN SAMPLE:\n" + buildStringPrettyStats(data)); } @@ -157,8 +186,8 @@ public void printStatsPretty(LinkedHashMap data) { * {@return StringBuilder with stats written into it in a pretty way} */ public StringBuilder buildStringPrettyStats(LinkedHashMap data) { - StringBuilder outputString = new StringBuilder(); - outputString.append(String.format("\n %-40s %n", "CONSTRAINT RELATED STATS\n")); + StringBuilder outputString = new StringBuilder(); + outputString.append(String.format("\n %-40s %n", "CONSTRAINT RELATED STATS\n")); for (Map.Entry entry : data.entrySet()) { if (entry.getValue() instanceof Map) { Map nestedMap = (Map) entry.getValue(); @@ -179,24 +208,24 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) * @param data: the previously computed data packaged line by line: String names the stat, Object holds the data. */ public void printStats(LinkedHashMap data) { - FeatJAR.log().message("STATISTICS ABOUT GIVEN SAMPLE:\n" + data); + FeatJAR.log().message("STATISTICS ABOUT GIVEN SAMPLE:\n" + data); + } + + /** + * + * {@return brief description of this class} + */ + @Override + public Optional getDescription() { + return Optional.of("Prints out statistics about a given sample set."); + } + + /** + * + * {@return short name of this class} + */ + @Override + public Optional getShortName() { + return Optional.of("printSampleStats"); } - - /** - * - * {@return brief description of this class} - */ - @Override - public Optional getDescription() { - return Optional.of("Prints out statistics about a given sample set."); - } - - /** - * - * {@return short name of this class} - */ - @Override - public Optional getShortName() { - return Optional.of("printSampleStats"); - } } diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index f0198980..d945da34 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -225,7 +225,7 @@ public void computeUniformity() { assertEquals(1, result.get("Transactions_AssignmentsSample_selected")); assertEquals(26, result.get("FeatureModel Valid")); assertEquals(3, result.get("AssignmentsSample Valid")); - + assertEquals(0, result.get("ConfigDB_FeatureModel_undefined")); assertEquals(0, result.get("ConfigDB_AssignmentsSample_undefined")); assertEquals(1, result.get("API_AssignmentsSample_undefined")); @@ -236,7 +236,7 @@ public void computeUniformity() { assertEquals(0, result.get("Windows_AssignmentsSample_undefined")); assertEquals(0, result.get("Linux_AssignmentsSample_undefined")); assertEquals(0, result.get("Transactions_AssignmentsSample_undefined")); - + assertEquals(0, result.get("ConfigDB_FeatureModel_deselected")); assertEquals(0, result.get("ConfigDB_AssignmentsSample_deselected")); assertEquals(0, result.get("API_FeatureModel_deselected")); @@ -267,7 +267,7 @@ public void computeUniformity() { assertEquals(((float) 2 / 3) - ((float) 13 / 26), result.get("Windows_selected")); assertEquals(((float) 1 / 3) - ((float) 13 / 26), result.get("Linux_selected")); assertEquals(((float) 1 / 3) - ((float) 12 / 26), result.get("Transactions_selected")); - + assertEquals(0, result.get("ConfigDB_deselected")); assertEquals(((float) 0 / 3) - (float) 0 / (float) 26, result.get("API_deselected")); assertEquals(0, result.get("OS_deselected")); @@ -276,8 +276,8 @@ public void computeUniformity() { assertEquals(((float) 2 / 3) - ((float) 10 / 26), result.get("Delete_deselected")); assertEquals(((float) 1 / 3) - ((float) 13 / 26), result.get("Windows_deselected")); assertEquals(((float) 2 / 3) - ((float) 13 / 26), result.get("Linux_deselected")); - assertEquals(((float) 2 / 3) - ((float)14 / 26), result.get("Transactions_deselected")); - + assertEquals(((float) 2 / 3) - ((float) 14 / 26), result.get("Transactions_deselected")); + assertEquals(0, result.get("ConfigDB_undefined")); assertEquals(((float) 1 / 3) - (float) 0 / (float) 26, result.get("API_undefined")); assertEquals(1, result.get("OS_undefined")); diff --git a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java index c39062f3..41aaa652 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java @@ -1,15 +1,28 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model.cli; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Paths; -import java.util.LinkedHashMap; - -import org.junit.jupiter.api.Test; - import de.featjar.base.FeatJAR; import de.featjar.base.data.Result; import de.featjar.base.io.IO; @@ -17,17 +30,21 @@ import de.featjar.feature.model.io.FeatureModelFormats; import de.featjar.formula.assignment.BooleanAssignmentList; import de.featjar.formula.io.BooleanAssignmentListFormats; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Paths; +import java.util.LinkedHashMap; +import org.junit.jupiter.api.Test; public class PrintSampleStatisticsTest { - private final String fmPath = "src/test/java/de/featjar/feature/model/cli/resources/DB_FeatureModel.xml"; - private final String configPath = "src/test/java/de/featjar/feature/model/cli/resources/DB_configs.csv"; - PrintSampleStatistics printSampleStats = new PrintSampleStatistics(); + private final String fmPath = "src/test/java/de/featjar/feature/model/cli/resources/DB_FeatureModel.xml"; + private final String configPath = "src/test/java/de/featjar/feature/model/cli/resources/DB_configs.csv"; + PrintSampleStatistics printSampleStats = new PrintSampleStatistics(); - @Test + @Test void inputTest() { - int exit_code = FeatJAR.runTest( - "printSampleStats", "--inputFM", fmPath,"--inputConfig", configPath); + int exit_code = FeatJAR.runTest("printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath); assertEquals(0, exit_code); } @@ -37,18 +54,12 @@ void noInput() { assertEquals(1, FeatJAR.runTest("printSampleStats", "--inputFM")); assertEquals(1, FeatJAR.runTest("printSampleStats")); } - + @Test void outputWithFileValidExtension() throws IOException { int exit_code = FeatJAR.runTest( - "printSampleStats", - "--inputFM", - fmPath, - "--inputConfig", - configPath, - "--output", - "model.csv"); + "printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath, "--output", "model.csv"); assertEquals(0, exit_code); assertTrue(Files.exists(Paths.get("model.csv")) && !Files.isDirectory(Paths.get("model.csv"))); Files.deleteIfExists(Paths.get("model.csv")); @@ -58,13 +69,7 @@ void outputWithFileValidExtension() throws IOException { void outputWithFileInvalidExtension() { int exit_code = FeatJAR.runTest( - "printSampleStats", - "--inputFM", - fmPath, - "--inputConfig", - configPath, - "--output", - "model.pdf"); + "printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath, "--output", "model.pdf"); assertEquals(1, exit_code); } @@ -72,26 +77,22 @@ void outputWithFileInvalidExtension() { void outputWithoutFileExtension() { int exit_code = FeatJAR.runTest( - "printSampleStats", - "--inputFM", - fmPath, - "--inputConfig", - configPath, - "--output", - "desktop/folder"); + "printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath, "--output", "desktop/folder"); assertEquals(1, exit_code); } @Test void printComparison() { - FeatJAR.initialize(); - Result loadedConfig = IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); + FeatJAR.initialize(); + Result loadedConfig = + IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); Result loadedFM = IO.load(Paths.get(fmPath), FeatureModelFormats.getInstance()); - - IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); - BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); - - String content = "{Number of Configurations=6, Number of Variables=9, Distribution of feature selection={selected=24, deselected=20, undefined=10}, Feature counter={ConfigDB_selected=6, ConfigDB_deselected=0, ConfigDB_undefined=0, API_selected=2, API_deselected=1, API_undefined=3, OS_selected=0, OS_deselected=0, OS_undefined=6, Get_selected=6, Get_deselected=0, Get_undefined=0, Put_selected=2, Put_deselected=3, Put_undefined=1, Delete_selected=1, Delete_deselected=5, Delete_undefined=0, Windows_selected=3, Windows_deselected=3, Windows_undefined=0, Linux_selected=2, Linux_deselected=4, Linux_undefined=0, Transactions_selected=2, Transactions_deselected=4, Transactions_undefined=0}, Uniformity={ConfigDB_selected=0.0, ConfigDB_deselected=0.0, ConfigDB_undefined=0.0, API_selected=-0.3333333, API_deselected=0.0, API_undefined=0.33333334, OS_selected=-1.0, OS_deselected=0.0, OS_undefined=1.0, Get_selected=0.46153843, Get_deselected=-0.46153846, Get_undefined=0.0, Put_selected=0.05128205, Put_deselected=-0.3846154, Put_undefined=0.33333334, Delete_selected=-0.2820513, Delete_deselected=0.2820513, Delete_undefined=0.0, Windows_selected=0.16666669, Windows_deselected=-0.16666666, Windows_undefined=0.0, Linux_selected=-0.16666666, Linux_deselected=0.16666669, Linux_undefined=0.0, Transactions_selected=-0.12820512, Transactions_deselected=0.12820512, Transactions_undefined=0.0}}"; + + IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); + BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); + + String content = + "{Number of Configurations=6, Number of Variables=9, Distribution of feature selection={selected=24, deselected=20, undefined=10}, Feature counter={ConfigDB_selected=6, ConfigDB_deselected=0, ConfigDB_undefined=0, API_selected=2, API_deselected=1, API_undefined=3, OS_selected=0, OS_deselected=0, OS_undefined=6, Get_selected=6, Get_deselected=0, Get_undefined=0, Put_selected=2, Put_deselected=3, Put_undefined=1, Delete_selected=1, Delete_deselected=5, Delete_undefined=0, Windows_selected=3, Windows_deselected=3, Windows_undefined=0, Linux_selected=2, Linux_deselected=4, Linux_undefined=0, Transactions_selected=2, Transactions_deselected=4, Transactions_undefined=0}, Uniformity={ConfigDB_selected=0.0, ConfigDB_deselected=0.0, ConfigDB_undefined=0.0, API_selected=-0.3333333, API_deselected=0.0, API_undefined=0.33333334, OS_selected=-1.0, OS_deselected=0.0, OS_undefined=1.0, Get_selected=0.46153843, Get_deselected=-0.46153846, Get_undefined=0.0, Put_selected=0.05128205, Put_deselected=-0.3846154, Put_undefined=0.33333334, Delete_selected=-0.2820513, Delete_deselected=0.2820513, Delete_undefined=0.0, Windows_selected=0.16666669, Windows_deselected=-0.16666666, Windows_undefined=0.0, Linux_selected=-0.16666666, Linux_deselected=0.16666669, Linux_undefined=0.0, Transactions_selected=-0.12820512, Transactions_deselected=0.12820512, Transactions_undefined=0.0}}"; String comparison = printSampleStats.collectStats(booleanAssignmentList, model).toString(); assertEquals(content, comparison); @@ -101,80 +102,80 @@ void printComparison() { @Test void prettyStringBuilder() { - FeatJAR.initialize(); - Result loadedConfig = IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); + FeatJAR.initialize(); + Result loadedConfig = + IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); Result loadedFM = IO.load(Paths.get(fmPath), FeatureModelFormats.getInstance()); - - IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); - BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); - - String comparison = - "\n CONSTRAINT RELATED STATS\n" - + " \n" - + "Number of Configurations : 6\n" - + "Number of Variables : 9\n" - + "Distribution of feature selection \n" - + " selected : 24\n" - + " deselected : 20\n" - + " undefined : 10\n" - + "Feature counter \n" - + " ConfigDB_selected : 6\n" - + " ConfigDB_deselected : 0\n" - + " ConfigDB_undefined : 0\n" - + " API_selected : 2\n" - + " API_deselected : 1\n" - + " API_undefined : 3\n" - + " OS_selected : 0\n" - + " OS_deselected : 0\n" - + " OS_undefined : 6\n" - + " Get_selected : 6\n" - + " Get_deselected : 0\n" - + " Get_undefined : 0\n" - + " Put_selected : 2\n" - + " Put_deselected : 3\n" - + " Put_undefined : 1\n" - + " Delete_selected : 1\n" - + " Delete_deselected : 5\n" - + " Delete_undefined : 0\n" - + " Windows_selected : 3\n" - + " Windows_deselected : 3\n" - + " Windows_undefined : 0\n" - + " Linux_selected : 2\n" - + " Linux_deselected : 4\n" - + " Linux_undefined : 0\n" - + " Transactions_selected : 2\n" - + " Transactions_deselected : 4\n" - + " Transactions_undefined : 0\n" - + "Uniformity \n" - + " ConfigDB_selected : 0.0\n" - + " ConfigDB_deselected : 0.0\n" - + " ConfigDB_undefined : 0.0\n" - + " API_selected : -0.3333333\n" - + " API_deselected : 0.0\n" - + " API_undefined : 0.33333334\n" - + " OS_selected : -1.0\n" - + " OS_deselected : 0.0\n" - + " OS_undefined : 1.0\n" - + " Get_selected : 0.46153843\n" - + " Get_deselected : -0.46153846\n" - + " Get_undefined : 0.0\n" - + " Put_selected : 0.05128205\n" - + " Put_deselected : -0.3846154\n" - + " Put_undefined : 0.33333334\n" - + " Delete_selected : -0.2820513\n" - + " Delete_deselected : 0.2820513\n" - + " Delete_undefined : 0.0\n" - + " Windows_selected : 0.16666669\n" - + " Windows_deselected : -0.16666666\n" - + " Windows_undefined : 0.0\n" - + " Linux_selected : -0.16666666\n" - + " Linux_deselected : 0.16666669\n" - + " Linux_undefined : 0.0\n" - + " Transactions_selected : -0.12820512\n" - + " Transactions_deselected : 0.12820512\n" - + " Transactions_undefined : 0.0\n" - + ""; - + + IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); + BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); + + String comparison = "\n CONSTRAINT RELATED STATS\n" + + " \n" + + "Number of Configurations : 6\n" + + "Number of Variables : 9\n" + + "Distribution of feature selection \n" + + " selected : 24\n" + + " deselected : 20\n" + + " undefined : 10\n" + + "Feature counter \n" + + " ConfigDB_selected : 6\n" + + " ConfigDB_deselected : 0\n" + + " ConfigDB_undefined : 0\n" + + " API_selected : 2\n" + + " API_deselected : 1\n" + + " API_undefined : 3\n" + + " OS_selected : 0\n" + + " OS_deselected : 0\n" + + " OS_undefined : 6\n" + + " Get_selected : 6\n" + + " Get_deselected : 0\n" + + " Get_undefined : 0\n" + + " Put_selected : 2\n" + + " Put_deselected : 3\n" + + " Put_undefined : 1\n" + + " Delete_selected : 1\n" + + " Delete_deselected : 5\n" + + " Delete_undefined : 0\n" + + " Windows_selected : 3\n" + + " Windows_deselected : 3\n" + + " Windows_undefined : 0\n" + + " Linux_selected : 2\n" + + " Linux_deselected : 4\n" + + " Linux_undefined : 0\n" + + " Transactions_selected : 2\n" + + " Transactions_deselected : 4\n" + + " Transactions_undefined : 0\n" + + "Uniformity \n" + + " ConfigDB_selected : 0.0\n" + + " ConfigDB_deselected : 0.0\n" + + " ConfigDB_undefined : 0.0\n" + + " API_selected : -0.3333333\n" + + " API_deselected : 0.0\n" + + " API_undefined : 0.33333334\n" + + " OS_selected : -1.0\n" + + " OS_deselected : 0.0\n" + + " OS_undefined : 1.0\n" + + " Get_selected : 0.46153843\n" + + " Get_deselected : -0.46153846\n" + + " Get_undefined : 0.0\n" + + " Put_selected : 0.05128205\n" + + " Put_deselected : -0.3846154\n" + + " Put_undefined : 0.33333334\n" + + " Delete_selected : -0.2820513\n" + + " Delete_deselected : 0.2820513\n" + + " Delete_undefined : 0.0\n" + + " Windows_selected : 0.16666669\n" + + " Windows_deselected : -0.16666666\n" + + " Windows_undefined : 0.0\n" + + " Linux_selected : -0.16666666\n" + + " Linux_deselected : 0.16666669\n" + + " Linux_undefined : 0.0\n" + + " Transactions_selected : -0.12820512\n" + + " Transactions_deselected : 0.12820512\n" + + " Transactions_undefined : 0.0\n" + + ""; + LinkedHashMap map = printSampleStats.collectStats(booleanAssignmentList, model); assertEquals(comparison, printSampleStats.buildStringPrettyStats(map).toString()); FeatJAR.deinitialize(); From a1b31b2860721fb7daa0a697486b27eb3fe10453 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 14:05:04 +0200 Subject: [PATCH 215/257] style: use specific filenames for tests. fix: check for negative values while building PieCharts. --- .gitignore | 2 +- .../visualization/AVisualizeFeatureModelStats.java | 5 ++--- .../visualization/VisualizeFeatureModelStatsTest.java | 7 +++++-- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.gitignore b/.gitignore index dc235c27..98eecc93 100644 --- a/.gitignore +++ b/.gitignore @@ -7,5 +7,5 @@ /bin/ /model_invalidInput.dot /export.pdf -/src/test/java/de/featjar/feature/model/visualization/model.pdf +/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.pdf /src/main/java/de/featjar/feature/model/analysis/visualization/Testtmp.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index b1e2fda8..091214e0 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -238,9 +238,8 @@ private HashMap extractAnalysisMap() { continue; } - boolean anyNegativeValues = treeData.values().stream() - .map(value -> (Double) value) - .anyMatch(value -> value < 0); + boolean anyNegativeValues = + treeData.values().stream().map(value -> (Double) value).anyMatch(value -> value < 0); if (anyNegativeValues) { FeatJAR.log().error("Pie chart cannot be built with negative values"); continue; diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index 650b240b..c40b050f 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -48,7 +48,8 @@ public class VisualizeFeatureModelStatsTest { AnalysisTree mediumTree = getMediumAnalysisTree(); AnalysisTree doubleTree = getDoubleTree(); - String defaultExportName = "src/test/java/de/featjar/feature/model/visualization/model.pdf"; + String defaultExportName = + "src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.pdf"; /** * Helper function. @@ -216,7 +217,9 @@ void invalidPDFPath() { @Test void pdfExportWithFolderCreation() throws IOException { String[] allPaths = { - "export.pdf", "Visualizer Test Folder/export.pdf", "Visualizer Test Folder/Nested Folder/export.pdf" + "pdfExportWithFolderCreation.pdf", + "Visualizer Test Folder/pdfExportWithFolderCreation.pdf", + "Visualizer Test Folder/Nested Folder/pdfExportWithFolderCreation.pdf" }; // cleanup From e0122a721e55b697b679cf3df32549713f879d03 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Thu, 16 Oct 2025 14:06:47 +0200 Subject: [PATCH 216/257] fix: changed name of root of resulting analysistree --- .../de/featjar/feature/model/cli/PrintSampleStatistics.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java index 1334f4e9..20e4bb64 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java @@ -113,7 +113,7 @@ public int run(OptionList optionParser) { private void writeTo(Path path, LinkedHashMap data) { String type = IO.getFileExtension(path); - Result> tree = AnalysisTreeTransformer.hashMapToTree(data, type); + Result> tree = AnalysisTreeTransformer.hashMapToTree(data, "Analysis"); try { switch (type) { From ed088772d2f48b67cbad23154311c652cbeb2bfa Mon Sep 17 00:00:00 2001 From: BvH19 <49963497+BvH19@users.noreply.github.com> Date: Thu, 16 Oct 2025 14:10:35 +0200 Subject: [PATCH 217/257] doc: added warning about prototype method --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 091214e0..b04d724b 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -251,6 +251,7 @@ private HashMap extractAnalysisMap() { /** * Premade builder for box charts that you can use when implementing {@link #buildCharts()}. + * Not ready yet * @return list containing one chart per tree in the feature model */ protected ArrayList> buildBoxCharts() { From 5503cb2e7d3b42400e965fe5de29b9bf08363e48 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 16 Oct 2025 14:15:26 +0200 Subject: [PATCH 218/257] refactor: refactoring of functionality regarding --format option, style improvements, added tests --- .../cli/ConfigurationFormatConversion.java | 113 +++++++---- .../ConfigurationFormatConversionTest.java | 190 ++++++++++-------- 2 files changed, 177 insertions(+), 126 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 9581a0db..fa805c1d 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -69,25 +69,31 @@ public String toString() { .filter(IFormat::supportsWrite) .map(IFormat::getFileExtension) .collect(Collectors.toList()); - + private static final List supportedOutputFileNames = BooleanAssignmentListFormats.getInstance().getExtensions().stream() .filter(IFormat::supportsWrite) - .map(f -> f.getName() + " (" + f.getFileExtension() + ")") + .map(f -> f.getName().toLowerCase()) + .collect(Collectors.toList()); + + private static final List supportedOutputFileNamesWithExtensions = + BooleanAssignmentListFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .map(f -> f.getName().toLowerCase() + " (." + f.getFileExtension() + ")") .collect(Collectors.toList()); public static final Option INPUT_OPTION = Option.newOption("input", Option.PathParser) .setDescription("Path to input file. Accepted File Types: " + supportedInputFileExtensions); public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) - .setDescription("Path to output file. Accepted File Types: " + supportedOutputFileNames); + .setDescription("Path to output file. Accepted File Types: " + supportedOutputFileNamesWithExtensions); public static final Option OVERWRITE = Option.newFlag("overwrite").setDescription("Overwrite existing file at output path."); - public static final Option FORMAT_TYPE = Option.newEnumOption("format", TypeTXT.class) - .setDescription("Specification necessary if output is desired to be .txt"); //TODO change to list object supportedOutputFileExtensions. - // man soll extension angeben können und in die datei, die bei output angegeben sind unabhängig von der angegebenen extension im output path speichern können + public static final Option FORMAT_TYPE = Option.newStringEnumOption( + "format", supportedOutputFileNames.toArray(new String[0])) + .setDescription("Format can be specified. Necessary if output path has .list extension."); /** * @return all options registered for the calling class. @@ -104,38 +110,46 @@ public final List> getOptions() { * 1 on invalid input or output path * 2 on unsupported input or output file extension * 3 on failure to save BooleanAssignmentList because file already exists on path directory and --overwrite flag is not used + * TODO 4 on conflicting .list extension in outputPath and no existing --format to specify the .list type */ @Override public int run(OptionList optionParser) { - // validating input/output - String intputFileExtension = - IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get().toString()); - String outputFileExtension = - IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get().toString()); - if (!checkIfInputOutputIsPresent(optionParser)) { - System.out.println("HEEEEEEEEEEEERE"); return 1; } ; + String format_type = ""; + if (optionParser.getResult(FORMAT_TYPE).isPresent()) { + format_type = optionParser.getResult(FORMAT_TYPE).get(); + + } else if (optionParser.getResult(OUTPUT_OPTION).get().toString().endsWith(".list")) { + + String outputBooleanAssignmentListFormat = + BooleanAssignmentListFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .map(f -> f.getName().toLowerCase()) + .filter(f -> f.endsWith("list")) + .collect(Collectors.joining("', '")); + + FeatJAR.log() + .error("Specify format for .list file using the --format command. Possible options: '" + + outputBooleanAssignmentListFormat + "'."); + return 4; + } + + String intputFileExtension = + IO.getFileExtension(optionParser.getResult(INPUT_OPTION).get().toString()); + String outputFileExtension = + IO.getFileExtension(optionParser.getResult(OUTPUT_OPTION).get().toString()); + if (!checkIfFileExtensionsValid(intputFileExtension, outputFileExtension)) { return 2; } - // --input and --typeTXT allow for conflicting file formats to be specified, in that case a warning is printed. - // In that case format of typeTXT is prioritized. - if (outputFileExtension != "list" && optionParser.getResult(FORMAT_TYPE).isPresent()) { - FeatJAR.log() - .warning("Conflicting command line options: " + outputFileExtension.toLowerCase() - + " file type in Path and .txt file type due to --typeTXT.\n " - + "Continuing to write with " - + optionParser.getResult(FORMAT_TYPE).get().description + " format into ." - + outputFileExtension.toLowerCase() + " file."); - FeatJAR.log() - .warning( - "Once written, data cannot be read from a txt file. Do not delete source file for this conversion."); + if (format_type.endsWith("list")) { + FeatJAR.log().warning("No parser exists for '.list' files."); } // loading list from input path @@ -143,7 +157,9 @@ public int run(OptionList optionParser) { optionParser.getResult(INPUT_OPTION).orElseThrow(), BooleanAssignmentListFormats.getInstance()) .get(); - return saveFile(optionParser.getResult(OUTPUT_OPTION).orElseThrow(), list, optionParser.get(OVERWRITE)); + // writing data to file + return saveFile( + optionParser.getResult(OUTPUT_OPTION).orElseThrow(), list, format_type, optionParser.get(OVERWRITE)); } /** @@ -195,15 +211,38 @@ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { * @return 0 on success * */ - public int saveFile(Path outputPath, BooleanAssignmentList inputList, boolean overwriteDemanded) { - - // Dynamically gathers corresponding BooleanAssignmentListFormat for given file extension. - Optional> outputFormats = - BooleanAssignmentListFormats.getInstance().getExtensions().stream() - .filter(IFormat::supportsWrite) - .filter(formatTemp -> - Objects.equals(IO.getFileExtension(outputPath), formatTemp.getFileExtension())) - .findFirst(); + public int saveFile( + Path outputPath, BooleanAssignmentList inputList, String formatType, boolean overwriteDemanded) { + + Optional> outputBooleanAssignmentListFormat = null; + + // if --format specifies the format + if (supportedOutputFileNames.contains(formatType)) { + + outputBooleanAssignmentListFormat = BooleanAssignmentListFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .filter(f -> f.getName().toLowerCase().equals(formatType.toLowerCase())) + .findFirst(); + + // user can specify different formats by using both --format and a path with another file extension. In that + // case a warning is printed + if (!IO.getFileExtension(outputPath) + .equals(outputBooleanAssignmentListFormat.get().getFileExtension())) { + FeatJAR.log() + .warning("Writing using the " + + outputBooleanAssignmentListFormat.get().getName() + " format into a ." + + IO.getFileExtension(outputPath) + " file."); + } + + // automatically extracting format from output path + } else { + outputBooleanAssignmentListFormat = BooleanAssignmentListFormats.getInstance().getExtensions().stream() + .filter(IFormat::supportsWrite) + .filter(formatTemp -> + Objects.equals(IO.getFileExtension(outputPath), formatTemp.getFileExtension())) + .findFirst(); + } + try { if (Files.exists(outputPath)) { if (overwriteDemanded) { @@ -215,12 +254,12 @@ public int saveFile(Path outputPath, BooleanAssignmentList inputList, boolean ov return 3; } } - IO.save(inputList, outputPath, outputFormats.get()); + IO.save(inputList, outputPath, outputBooleanAssignmentListFormat.get()); + FeatJAR.log().message("Output list successfully saved at: " + outputPath); } catch (Exception e) { FeatJAR.log().error(e); } - FeatJAR.log().message("Output list successfully saved at: " + outputPath); return 0; } diff --git a/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java index 099f53b3..a12f6d42 100644 --- a/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/ConfigurationFormatConversionTest.java @@ -60,30 +60,30 @@ void csvToOtherFormatsTest() throws IOException { assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); - outputPath = "list_csvToList.list"; + outputPath = "list_csvToVariablelist.list"; exit_code = FeatJAR.runTest( "configurationFormatConversion", "--input", inputPath, "--output", outputPath, - "--typeTXT", - "default_txt", - "--overwrite"); + "--overwrite", + "--format", + "variablelist"); assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); Files.deleteIfExists(Paths.get(outputPath)); - outputPath = "list_csvToSimpleList.list"; + outputPath = "list_csvToSimpleLiterallist.list"; exit_code = FeatJAR.runTest( "configurationFormatConversion", "--input", inputPath, "--output", outputPath, - "--typeTXT", - "simple_txt", - "--overwrite"); + "--overwrite", + "--format", + "literallist"); assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); Files.deleteIfExists(Paths.get(outputPath)); @@ -128,30 +128,30 @@ void dimacsToOtherFormatsTest() throws IOException { assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); - outputPath = "list_dimacsToList.list"; + outputPath = "list_dimacsToVariablelist.list"; exit_code = FeatJAR.runTest( "configurationFormatConversion", "--input", inputPath, "--output", outputPath, - "--typeTXT", - "default_txt", - "--overwrite"); + "--overwrite", + "--format", + "variablelist"); assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); Files.deleteIfExists(Paths.get(outputPath)); - outputPath = "list_dimacsToSimpleList.list"; + outputPath = "list_dimacsToLiteralist.list"; exit_code = FeatJAR.runTest( "configurationFormatConversion", "--input", inputPath, "--output", outputPath, - "--typeTXT", - "simple_txt", - "--overwrite"); + "--overwrite", + "--format", + "literallist"); assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); Files.deleteIfExists(Paths.get(outputPath)); @@ -196,30 +196,30 @@ void binaryToOtherFormatsTest() throws IOException { assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); - outputPath = "list_binaryToList.list"; + outputPath = "list_binaryToVariablelist.list"; exit_code = FeatJAR.runTest( "configurationFormatConversion", "--input", inputPath, "--output", outputPath, - "--typeTXT", - "default_txt", - "--overwrite"); + "--overwrite", + "--format", + "variablelist"); assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); Files.deleteIfExists(Paths.get(outputPath)); - outputPath = "list_binaryToSimpleList.list"; + outputPath = "list_binaryToSimpleLiterallist.list"; exit_code = FeatJAR.runTest( "configurationFormatConversion", "--input", inputPath, "--output", outputPath, - "--typeTXT", - "simple_txt", - "--overwrite"); + "--overwrite", + "--format", + "literallist"); assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); Files.deleteIfExists(Paths.get(outputPath)); @@ -243,7 +243,7 @@ void binaryToOtherFormatsTest() throws IOException { } /** - * Tests ... + * Tests conversion across different formats back to the original format * */ @Test @@ -255,42 +255,21 @@ void roundTripTest() throws IOException { // csv -> binary String outputPath = "list_csvToBinaryRoundTrip.bin"; int exit_code = FeatJAR.runTest( - "configurationFormatConversion", - "--input", - OriginalInputPath, - "--output", - outputPath, - "--typeTXT", - "simple_txt", - "--overwrite"); + "configurationFormatConversion", "--input", OriginalInputPath, "--output", outputPath, "--overwrite"); String inputPath = outputPath; assertEquals(0, exit_code); // binary -> dimacs outputPath = "list_binaryToDimacsRoundTrip.dimacs"; exit_code = FeatJAR.runTest( - "configurationFormatConversion", - "--input", - inputPath, - "--output", - outputPath, - "--typeTXT", - "simple_txt", - "--overwrite"); + "configurationFormatConversion", "--input", inputPath, "--output", outputPath, "--overwrite"); inputPath = outputPath; assertEquals(0, exit_code); // dimacs -> csv outputPath = "list_dimacsToCSVRoundTrip.csv"; exit_code = FeatJAR.runTest( - "configurationFormatConversion", - "--input", - inputPath, - "--output", - outputPath, - "--typeTXT", - "simple_txt", - "--overwrite"); + "configurationFormatConversion", "--input", inputPath, "--output", outputPath, "--overwrite"); assertEquals(0, exit_code); FeatJAR.initialize(); @@ -308,99 +287,132 @@ void roundTripTest() throws IOException { } /** - * Tests ... + * Tests error handling with no input/output * */ @Test - void errorHandlingTest() throws IOException { - - String OriginalInputPath = - "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList."; - String OriginalInputPathCSV = OriginalInputPath + "csv"; - String OriginalInputPathTXT = OriginalInputPath + "txt"; // invalid input path - String OriginalInputPathBIN = OriginalInputPath + "bin"; - String OriginalInputPathXML = OriginalInputPath + "xml"; // invalid input path - - String outputPath = "list_csvToBinaryRoundTrip."; - String outputPathCSV = outputPath + "csv"; - String outputPathBIN = outputPath + "bin"; - String outputPathXML = outputPath + "xml"; // invalid output path + void missingInputOrOutputPathTest() throws IOException { + // missing input path assertEquals( 1, FeatJAR.runTest( "configurationFormatConversion", "--input", "--output", - outputPathCSV, - "--typeTXT", + "list_errorHandling.csv", + "--format", "simple_txt", - "--overwrite")); // missing input path + "--overwrite")); + + // missing output path assertEquals( 1, FeatJAR.runTest( "configurationFormatConversion", "--input", - OriginalInputPathCSV, + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.csv", "--output", - "--typeTXT", + "--format", "simple_txt", - "--overwrite")); // missing output path + "--overwrite")); + } + + /** + * Tests error handling with invalid input/output + * + */ + @Test + void invalidInputTypeOrOutputType() throws IOException { + // xml is not a supported input type assertEquals( 2, FeatJAR.runTest( "configurationFormatConversion", "--input", - OriginalInputPathXML, + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.xml", "--output", - outputPathBIN, - "--typeTXT", + "list_errorHandling.bin", + "--format", "simple_txt", - "--overwrite")); // xml is not a supported input type + "--overwrite")); + + // txt is not a supported input type assertEquals( 2, FeatJAR.runTest( "configurationFormatConversion", "--input", - OriginalInputPathTXT, + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.txt", "--output", - outputPathBIN, - "--typeTXT", + "list_errorHandling.bin", + "--format", "simple_txt", - "--overwrite")); // txt is not a supported input type + "--overwrite")); + + // xml is not a supported output type assertEquals( 2, FeatJAR.runTest( "configurationFormatConversion", "--input", - OriginalInputPathBIN, + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.bin", "--output", - outputPathXML, - "--typeTXT", + "list_errorHandling.xml", + "--format", "simple_txt", - "--overwrite")); // xml is not a supported output type + "--overwrite")); + } + + /** + * Tests error handling with missing --overwrite option + * + */ + @Test + void overwriteOptionTest() throws IOException { + // creating file for next assertion assertEquals( 0, FeatJAR.runTest( "configurationFormatConversion", "--input", - OriginalInputPathBIN, + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.bin", "--output", - outputPathCSV, - "--typeTXT", + "list_errorHandling.csv", + "--format", "simple_txt", - "--overwrite")); // creating file for next assertion + "--overwrite")); + + // failure to overwrite file because --overwrite is missing assertEquals( 3, FeatJAR.runTest( "configurationFormatConversion", "--input", - OriginalInputPathBIN, + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.bin", + "--output", + "list_errorHandling.csv")); + + Files.deleteIfExists(Paths.get("list_errorHandling.csv")); + } + + /** + * Tests error handling when no format is specified with --format when using a .list path + * + */ + @Test + void unspecifiedFormatWhenListIsInOutputPath() throws IOException { + + // path with .list file but format not specified with --format + assertEquals( + 4, + FeatJAR.runTest( + "configurationFormatConversion", + "--input", + "src/test/java/de/featjar/feature/model/cli/resources/BooleanAssignmentLists/BooleanAssignmentList.bin", "--output", - outputPathCSV, - "--typeTXT", - "simple_txt")); // failure to overwrite file because --overwrite is missing + "example.list")); } } From 3028a2720441e38c08f9701c971a4608ed5b976f Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 16 Oct 2025 15:25:18 +0200 Subject: [PATCH 219/257] refactor: changed scope of variables in computional to public. --- .../ComputeDistributionFeatureSelections.java | 2 +- .../analysis/ComputeNumberConfigurations.java | 2 +- .../analysis/ComputeNumberVariables.java | 2 +- .../model/analysis/ComputeUniformity.java | 21 +++++++++++++------ .../model/computation/ComputeAtomsCount.java | 8 +++---- .../computation/ComputeAverageConstraint.java | 8 +++---- .../computation/ComputeFeatureDensity.java | 2 +- .../ComputeOperatorDistribution.java | 2 +- .../transformer/AnalysisTreeTransformer.java | 1 - .../model/analysis/SamplePropertiesTest.java | 2 ++ 10 files changed, 30 insertions(+), 20 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java b/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java index 1e0f4184..dcffde89 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java @@ -38,7 +38,7 @@ */ public class ComputeDistributionFeatureSelections extends AComputation> { - protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = + public static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); public ComputeDistributionFeatureSelections(IComputation booleanAssignmentList) { diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java index ea4a6b74..3223e259 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java @@ -36,7 +36,7 @@ */ public class ComputeNumberConfigurations extends AComputation { - protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = + public static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); public ComputeNumberConfigurations(IComputation booleanAssignmentList) { diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java index 9375cb31..fd7de7c7 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java @@ -36,7 +36,7 @@ */ public class ComputeNumberVariables extends AComputation { - protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = + public static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); public ComputeNumberVariables(IComputation booleanAssignmentList) { diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java index 4e7349a8..d9297dea 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -58,10 +58,10 @@ */ public class ComputeUniformity extends AComputation> { - protected static final Dependency BOOLEAN_ASSIGNMENT_LIST = + public static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); - protected static final Dependency FEATURE_MODEL = Dependency.newDependency(IFeatureModel.class); - protected static final Dependency ANALYSIS = Dependency.newDependency(Boolean.class); + public static final Dependency FEATURE_MODEL = Dependency.newDependency(IFeatureModel.class); + public static final Dependency ANALYSIS = Dependency.newDependency(Boolean.class); public ComputeUniformity(IComputation featureModel) { super( @@ -97,7 +97,7 @@ public Result> compute(List dependencyList, } // Calculate the number of valid configurations per feature in the full featureModel. - for (String varName : fmVariableMap.getVariableNames()) { + for (String varName : fmVariableMap.getVariableNames()) { Reference currentFormula = new Reference(new And((IFormula) fmFormula.getChildren().get(0), new Literal(varName))); currentFormula.setFreeVariables(((Reference) fmFormula).getFreeVariables()); @@ -133,19 +133,25 @@ public Result> compute(List dependencyList, // Calculate the number of valid configurations per feature in the full assignmentSample. int assignmentSolutionsCount = 0; for (BooleanAssignment booleanAssignment : booleanAssignmentList.getAll()) { + // save all Literals, Whether they are true and false, then add them to full formula with an And. LinkedList allLiterals = new LinkedList(); + // save the Name of the literals that are set to true/selected in the current assignment List currentSelectedAssignmentVariables = new LinkedList(); + // save the Name of the literals that are set to false/deselected in the current assignment List currentDeselectedAssignmentVariables = new LinkedList(); for (int index : booleanAssignment.get()) { + // Add selected Literal if (fmVariableMap.get(index).isPresent()) { allLiterals.add(new Literal(fmVariableMap.get(index).get())); currentSelectedAssignmentVariables.add( fmVariableMap.get(index).get()); + // Add deselected Literal } else if (fmVariableMap.get(Math.abs(index)).isPresent()) { currentDeselectedAssignmentVariables.add( fmVariableMap.get(Math.abs(index)).get()); allLiterals.add(new Not( new Literal(fmVariableMap.get(Math.abs(index)).get()))); + // shouldn't happen but just in case. } else { Result.empty(); } @@ -154,6 +160,7 @@ public Result> compute(List dependencyList, Reference currentFormula = new Reference(new And((IFormula) fmFormula.getChildren().get(0), currentIFormulaAssignment)); currentFormula.setFreeVariables(((Reference) fmFormula).getFreeVariables()); + // check if the formula is valid, if yes increase the count of the selected or deselected Literals in returnedMap. if (Computations.of((IFormula) currentFormula) .map(ComputeNNFFormula::new) .map(ComputeCNFFormula::new) @@ -173,7 +180,8 @@ public Result> compute(List dependencyList, } } } - + + // For valid assignment calculate and add the count of undefined Literals in the AssignmentLists for (String varName : fmVariableMap.getVariableNames()) { returnedMap.replace( varName + assignmentsSamplePrefix + "_undefined", @@ -186,7 +194,7 @@ public Result> compute(List dependencyList, - returnedMap.get(varName + featureModelPrefix + "_selected") - returnedMap.get(varName + featureModelPrefix + "_deselected")); } - + if (ANALYSIS.get(dependencyList)) { for (String varName : fmVariableMap.getVariableNames()) { float sampleShareSelected = @@ -217,4 +225,5 @@ public Result> compute(List dependencyList, } return Result.of(returnedMap); } + } diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index db0c4d57..078337ea 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -44,13 +44,13 @@ * @author Florian Beese * */ public class ComputeAtomsCount extends AComputation { - protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); // COUNTCONSTANTS decide if Atoms of type constants should be counted - protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); // COUNTVARIABLES decide if Atoms of type variable should be counted - protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); // COUNTBOOLEAN decide if Atoms of type True or False should be counted - protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); public ComputeAtomsCount(IComputation featureModel) { super( diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 34a5ff9b..43ff413d 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -45,10 +45,10 @@ * @author Florian Beese * */ public class ComputeAverageConstraint extends AComputation { - protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); - protected static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); - protected static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); - protected static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); + public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + public static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); public ComputeAverageConstraint(IComputation featureModel) { super( diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java index 95c71388..db08f1ae 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -44,7 +44,7 @@ * @author Florian Beese * */ public class ComputeFeatureDensity extends AComputation { - protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); public ComputeFeatureDensity(IComputation featureModel) { super(featureModel); diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java index 8832cb7c..5420ec21 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -42,7 +42,7 @@ * @author Florian Beese * */ public class ComputeOperatorDistribution extends AComputation> { - protected static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); public ComputeOperatorDistribution(IComputation featureModel) { super(featureModel); diff --git a/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java index e242a628..01176fef 100644 --- a/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java +++ b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java @@ -202,7 +202,6 @@ public static Result> yamlHashMapToTree(HashMap return Result.empty(); } if (!(currentElement.get(2) instanceof Double || currentElement.get(2) instanceof Integer)) { - System.out.println(currentElement.get(2).getClass()); FeatJAR.log() .error("The third element of an innermost element of the Map/YAML data structure " + "was not from the type String"); diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index c0644361..fc1d19f9 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -287,6 +287,8 @@ public void computeUniformity() { assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Windows_undefined")); assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Linux_undefined")); assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Transactions_undefined")); + + FeatJAR.deinitialize(); } private IFeatureTree generateMediumTree() { From fdb0cc991cb05108578b8f8844386f3f44df41ba Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Thu, 16 Oct 2025 15:39:00 +0200 Subject: [PATCH 220/257] feat: added flag to uniformity statistics unprocessed --- .../model/analysis/ComputeUniformity.java | 2 +- .../model/cli/PrintSampleStatistics.java | 41 +++++++++++++++---- 2 files changed, 33 insertions(+), 10 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java index b83a81d8..1b61d9c7 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -61,7 +61,7 @@ public class ComputeUniformity extends AComputation public static final Dependency BOOLEAN_ASSIGNMENT_LIST = Dependency.newDependency(BooleanAssignmentList.class); protected static final Dependency FEATURE_MODEL = Dependency.newDependency(IFeatureModel.class); - protected static final Dependency ANALYSIS = Dependency.newDependency(Boolean.class); + public static final Dependency ANALYSIS = Dependency.newDependency(Boolean.class); public ComputeUniformity(IComputation featureModel) { super( diff --git a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java index 20e4bb64..bf9047c9 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java @@ -66,6 +66,9 @@ public class PrintSampleStatistics extends ACommand { public static final Option PRETTY_PRINT = Option.newFlag("pretty").setDescription("Pretty prints the numbers"); + + public static final Option UNPROCESSED = + Option.newFlag("unprocessed").setDescription("Prints unprocessed data of uniformity statistics"); @Override public int run(OptionList optionParser) { @@ -87,8 +90,13 @@ public int run(OptionList optionParser) { LinkedHashMap data; IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); - - data = collectStats(booleanAssignmentList, model); + + if (optionParser.get(UNPROCESSED)) { + data = collectStats(booleanAssignmentList, model, true); + } else { + data = collectStats(booleanAssignmentList, model); + } + // if output path is specified, write statistics to file if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { @@ -139,6 +147,10 @@ private void writeTo(Path path, LinkedHashMap data) { } } + public LinkedHashMap collectStats(BooleanAssignmentList boolList, IFeatureModel model) { + return collectStats(boolList, model, false); + } + /** * Gathers statistics about given configuration set * @@ -146,7 +158,7 @@ private void writeTo(Path path, LinkedHashMap data) { * @param model - corresponding feature model * @return returns Map containing statistics */ - public LinkedHashMap collectStats(BooleanAssignmentList boolList, IFeatureModel model) { + public LinkedHashMap collectStats(BooleanAssignmentList boolList, IFeatureModel model, boolean unprocessed) { LinkedHashMap data = new LinkedHashMap<>(); data.put( @@ -163,12 +175,23 @@ public LinkedHashMap collectStats(BooleanAssignmentList boolList data.put( "Feature counter", Computations.of(boolList).map(ComputeFeatureCounter::new).compute()); - data.put( - "Uniformity", - Computations.of(model) - .map(ComputeUniformity::new) - .set(ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, boolList) - .compute()); + if (unprocessed) { + data.put( + "Uniformity", + Computations.of(model) + .map(ComputeUniformity::new) + .set(ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, boolList) + .set(ComputeUniformity.ANALYSIS, false) + .compute()); + } else { + data.put( + "Uniformity", + Computations.of(model) + .map(ComputeUniformity::new) + .set(ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, boolList) + .compute()); + } + return data; } /** From 3b1447c3105451a08f2b97fb55152f8887cb8204 Mon Sep 17 00:00:00 2001 From: BvH19 <49963497+BvH19@users.noreply.github.com> Date: Thu, 16 Oct 2025 15:48:19 +0200 Subject: [PATCH 221/257] style: properly removed code that was commented out --- src/main/java/de/featjar/feature/model/cli/PrintStatistics.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index c7ef28b2..ee90b81e 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -194,7 +194,6 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc HashMap computational_opDensity = Computations.of(model).map(ComputeOperatorDistribution::new).compute(); - // data.put("Operator Distribution", computational_opDensity); if (computational_opDensity.size() != 0) { data.put("Operator Distribution", computational_opDensity); } From 1054460cb2ace6f8ef2d93b5a11e9558b654be1c Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Thu, 16 Oct 2025 15:49:47 +0200 Subject: [PATCH 222/257] test: added test for unprocessed flag --- .../model/cli/PrintSampleStatistics.java | 18 +++++++++--------- .../model/cli/PrintSampleStatisticsTest.java | 7 +++++++ 2 files changed, 16 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java index bf9047c9..957a760d 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java @@ -66,7 +66,7 @@ public class PrintSampleStatistics extends ACommand { public static final Option PRETTY_PRINT = Option.newFlag("pretty").setDescription("Pretty prints the numbers"); - + public static final Option UNPROCESSED = Option.newFlag("unprocessed").setDescription("Prints unprocessed data of uniformity statistics"); @@ -90,13 +90,12 @@ public int run(OptionList optionParser) { LinkedHashMap data; IFeatureModel model = (IFeatureModel) loadedFM.orElseThrow(); BooleanAssignmentList booleanAssignmentList = (BooleanAssignmentList) loadedConfig.orElseThrow(); - + if (optionParser.get(UNPROCESSED)) { - data = collectStats(booleanAssignmentList, model, true); + data = collectStats(booleanAssignmentList, model, true); } else { - data = collectStats(booleanAssignmentList, model); + data = collectStats(booleanAssignmentList, model); } - // if output path is specified, write statistics to file if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { @@ -148,9 +147,9 @@ private void writeTo(Path path, LinkedHashMap data) { } public LinkedHashMap collectStats(BooleanAssignmentList boolList, IFeatureModel model) { - return collectStats(boolList, model, false); + return collectStats(boolList, model, false); } - + /** * Gathers statistics about given configuration set * @@ -158,7 +157,8 @@ public LinkedHashMap collectStats(BooleanAssignmentList boolList * @param model - corresponding feature model * @return returns Map containing statistics */ - public LinkedHashMap collectStats(BooleanAssignmentList boolList, IFeatureModel model, boolean unprocessed) { + public LinkedHashMap collectStats( + BooleanAssignmentList boolList, IFeatureModel model, boolean unprocessed) { LinkedHashMap data = new LinkedHashMap<>(); data.put( @@ -191,7 +191,7 @@ public LinkedHashMap collectStats(BooleanAssignmentList boolList .set(ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, boolList) .compute()); } - + return data; } /** diff --git a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java index 41aaa652..5b946cb0 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java @@ -96,6 +96,13 @@ void printComparison() { String comparison = printSampleStats.collectStats(booleanAssignmentList, model).toString(); assertEquals(content, comparison); + + String unprocessedString = + "{Number of Configurations=6, Number of Variables=9, Distribution of feature selection={selected=24, deselected=20, undefined=10}, Feature counter={ConfigDB_selected=6, ConfigDB_deselected=0, ConfigDB_undefined=0, API_selected=2, API_deselected=1, API_undefined=3, OS_selected=0, OS_deselected=0, OS_undefined=6, Get_selected=6, Get_deselected=0, Get_undefined=0, Put_selected=2, Put_deselected=3, Put_undefined=1, Delete_selected=1, Delete_deselected=5, Delete_undefined=0, Windows_selected=3, Windows_deselected=3, Windows_undefined=0, Linux_selected=2, Linux_deselected=4, Linux_undefined=0, Transactions_selected=2, Transactions_deselected=4, Transactions_undefined=0}, Uniformity={ConfigDB_FeatureModel_selected=26.0, ConfigDB_AssignmentsSample_selected=3.0, ConfigDB_FeatureModel_deselected=0.0, ConfigDB_AssignmentsSample_deselected=0.0, ConfigDB_FeatureModel_undefined=0.0, ConfigDB_AssignmentsSample_undefined=0.0, API_FeatureModel_selected=26.0, API_AssignmentsSample_selected=2.0, API_FeatureModel_deselected=0.0, API_AssignmentsSample_deselected=0.0, API_FeatureModel_undefined=0.0, API_AssignmentsSample_undefined=1.0, OS_FeatureModel_selected=26.0, OS_AssignmentsSample_selected=0.0, OS_FeatureModel_deselected=0.0, OS_AssignmentsSample_deselected=0.0, OS_FeatureModel_undefined=0.0, OS_AssignmentsSample_undefined=3.0, Get_FeatureModel_selected=14.0, Get_AssignmentsSample_selected=3.0, Get_FeatureModel_deselected=12.0, Get_AssignmentsSample_deselected=0.0, Get_FeatureModel_undefined=0.0, Get_AssignmentsSample_undefined=0.0, Put_FeatureModel_selected=16.0, Put_AssignmentsSample_selected=2.0, Put_FeatureModel_deselected=10.0, Put_AssignmentsSample_deselected=0.0, Put_FeatureModel_undefined=0.0, Put_AssignmentsSample_undefined=1.0, Delete_FeatureModel_selected=16.0, Delete_AssignmentsSample_selected=1.0, Delete_FeatureModel_deselected=10.0, Delete_AssignmentsSample_deselected=2.0, Delete_FeatureModel_undefined=0.0, Delete_AssignmentsSample_undefined=0.0, Windows_FeatureModel_selected=13.0, Windows_AssignmentsSample_selected=2.0, Windows_FeatureModel_deselected=13.0, Windows_AssignmentsSample_deselected=1.0, Windows_FeatureModel_undefined=0.0, Windows_AssignmentsSample_undefined=0.0, Linux_FeatureModel_selected=13.0, Linux_AssignmentsSample_selected=1.0, Linux_FeatureModel_deselected=13.0, Linux_AssignmentsSample_deselected=2.0, Linux_FeatureModel_undefined=0.0, Linux_AssignmentsSample_undefined=0.0, Transactions_FeatureModel_selected=12.0, Transactions_AssignmentsSample_selected=1.0, Transactions_FeatureModel_deselected=14.0, Transactions_AssignmentsSample_deselected=2.0, Transactions_FeatureModel_undefined=0.0, Transactions_AssignmentsSample_undefined=0.0, FeatureModel Valid=26.0, AssignmentsSample Valid=3.0}}"; + String unprocessedComparison = printSampleStats + .collectStats(booleanAssignmentList, model, true) + .toString(); + assertEquals(unprocessedString, unprocessedComparison); FeatJAR.deinitialize(); } From 14619a87c889b21e24cd26abc23450d5b1b32055 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 16 Oct 2025 15:57:47 +0200 Subject: [PATCH 223/257] chore: fixed minor issues --- .../feature/model/cli/ConfigurationFormatConversion.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index fa805c1d..050e7efb 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -110,15 +110,14 @@ public final List> getOptions() { * 1 on invalid input or output path * 2 on unsupported input or output file extension * 3 on failure to save BooleanAssignmentList because file already exists on path directory and --overwrite flag is not used - * TODO 4 on conflicting .list extension in outputPath and no existing --format to specify the .list type + * 4 on conflicting .list extension in outputPath and no existing --format to specify the .list type */ @Override public int run(OptionList optionParser) { if (!checkIfInputOutputIsPresent(optionParser)) { return 1; - } - ; + }; String format_type = ""; if (optionParser.getResult(FORMAT_TYPE).isPresent()) { @@ -207,6 +206,7 @@ private boolean checkIfInputOutputIsPresent(OptionList optionParser) { * Saves the opened BooleanAssignmentList as a different desired BooleanAssignmentList file. Automatically detects the appropriate format. Does error handling. * @param outputPath Full path to output file including extension. * @param inputList BooleanAssignmentList to be saved into the output file. + * @param formatType String that can specify format (csv, binary, dimacs, literallist, variablelist) * @param overwriteDemanded flag that decides whether existing output file with the same name should be overwritten. * @return 0 on success * From 297a73262379e2ed04c54f45aef2857f7d9b7325 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Thu, 16 Oct 2025 16:02:42 +0200 Subject: [PATCH 224/257] fix: solved an error that could happen if Feature model or assignments have no valid solution. --- .../model/analysis/ComputeUniformity.java | 37 +++++++++++++------ 1 file changed, 25 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java index d9297dea..e026f0e2 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -197,18 +197,31 @@ public Result> compute(List dependencyList, if (ANALYSIS.get(dependencyList)) { for (String varName : fmVariableMap.getVariableNames()) { - float sampleShareSelected = - returnedMap.get(varName + assignmentsSamplePrefix + "_selected") / assignmentSolutionsCount; - float featureShareSelected = - returnedMap.get(varName + featureModelPrefix + "_selected") / solutionsCount; - float sampleShareDeselected = - returnedMap.get(varName + assignmentsSamplePrefix + "_deselected") / assignmentSolutionsCount; - float featureShareDeselected = - returnedMap.get(varName + featureModelPrefix + "_deselected") / solutionsCount; - float sampleShareUndefined = - returnedMap.get(varName + assignmentsSamplePrefix + "_undefined") / assignmentSolutionsCount; - float featureShareUndefined = - returnedMap.get(varName + featureModelPrefix + "_undefined") / solutionsCount; + float sampleShareSelected = 0; + float sampleShareDeselected = 0; + float sampleShareUndefined = 0; + float featureShareSelected = 0; + float featureShareDeselected = 0; + float featureShareUndefined = 0; + + if (assignmentSolutionsCount > 0) { + sampleShareSelected = + returnedMap.get(varName + assignmentsSamplePrefix + "_selected") / assignmentSolutionsCount; + sampleShareDeselected = + returnedMap.get(varName + assignmentsSamplePrefix + "_deselected") / assignmentSolutionsCount; + sampleShareUndefined = + returnedMap.get(varName + assignmentsSamplePrefix + "_undefined") / assignmentSolutionsCount; + } + + if (solutionsCount > 0) { + featureShareSelected = + returnedMap.get(varName + featureModelPrefix + "_selected") / solutionsCount; + featureShareDeselected = + returnedMap.get(varName + featureModelPrefix + "_deselected") / solutionsCount; + featureShareUndefined = + returnedMap.get(varName + featureModelPrefix + "_undefined") / solutionsCount; + } + returnedMap.remove(varName + assignmentsSamplePrefix + "_selected"); returnedMap.remove(varName + featureModelPrefix + "_selected"); returnedMap.remove(varName + assignmentsSamplePrefix + "_deselected"); From ebca263828172970f3212e3027917518cfb29121 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 16 Oct 2025 16:18:46 +0200 Subject: [PATCH 225/257] fix: removed a minor issues in a string used for a test --- .../de/featjar/feature/model/cli/PrintStatisticsTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index fb1cd222..d68b017a 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -122,7 +122,7 @@ void outputWithoutFileExtension() throws IOException { "model_outputWithoutFileExtension", "--overwrite"); assertEquals(1, exit_code); - } + } /** * Testing whether collecting statistics with scope specified to ALL actually returns values for all parameters. @@ -130,7 +130,7 @@ void outputWithoutFileExtension() throws IOException { @Test void scopeAll() throws IOException { String content = - "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN, [Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + "{Number of Atoms=0, Feature Density=0.0, Average Constraints=0.0, [Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; String comparison = printStats.collectStats(minimalModel, AnalysesScope.ALL).toString(); assertEquals(content, comparison); @@ -155,7 +155,7 @@ void scopeTreeRelated() throws IOException { */ @Test void scopeConstraintRelated() throws IOException { - String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=NaN}" + ""; + String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=0.0}" + ""; String comparison = printStats .collectStats(minimalModel, AnalysesScope.CONSTRAINT_RELATED) .toString(); @@ -219,7 +219,7 @@ void jsonOuputTest() throws IOException { assertEquals(tree.print(), tree_expected.print()); assertEquals(0, exit_code); - Files.deleteIfExists(Paths.get("model_jsonOutputTest.json")); + Files.deleteIfExists(Paths.get("model_jsonOuputTest.json")); } /** From 557f74a657b165ed4a922b0a935f503d77c0d0ae Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 16:19:00 +0200 Subject: [PATCH 226/257] fix: Pie Chart builder now checks for negative value without problematic Double cast --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 2 +- .../feature/model/analysis/SimpleTreePropertiesTest.java | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 091214e0..fe516ab0 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -239,7 +239,7 @@ private HashMap extractAnalysisMap() { } boolean anyNegativeValues = - treeData.values().stream().map(value -> (Double) value).anyMatch(value -> value < 0); + treeData.values().stream().map(value -> (Number) value).anyMatch(value -> value.doubleValue() < 0); if (anyNegativeValues) { FeatJAR.log().error("Pie chart cannot be built with negative values"); continue; diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 39e9814e..0f2fe674 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -23,6 +23,7 @@ import static org.junit.jupiter.api.Assertions.*; import de.featjar.Common; +import de.featjar.base.FeatJAR; import de.featjar.base.computation.Computations; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; From bbfaa698fb82ba9fe2945fcd5951e90de6ac6e61 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Thu, 16 Oct 2025 16:32:21 +0200 Subject: [PATCH 227/257] test fix: invalidPDFPath() test now works under Linux and Windows! --- .../model/visualization/VisualizeFeatureModelStatsTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index c40b050f..e2b659db 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -211,7 +211,7 @@ void changeChartWidth() { void invalidPDFPath() { VisualizeGroupDistribution vizGroup; vizGroup = new VisualizeGroupDistribution(mediumTree); - assertEquals(1, vizGroup.exportChartToPDF("?/x.xml")); + assertEquals(1, vizGroup.exportChartToPDF("\0/x.xml")); } @Test From 452e7f0d54e97b0c56be3a02758669406af43173 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Thu, 16 Oct 2025 17:04:36 +0200 Subject: [PATCH 228/257] feat: adding command paramenter for visualize statistics. WIP --- .../feature/model/cli/PrintStatistics.java | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index ee90b81e..ddad1d1a 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -41,10 +41,14 @@ import de.featjar.feature.model.computation.ComputeFeatureDensity; import de.featjar.feature.model.computation.ComputeOperatorDistribution; import de.featjar.feature.model.io.FeatureModelFormats; +import de.featjar.feature.model.analysis.visualization.*; import de.featjar.feature.model.io.csv.CSVAnalysisFormat; import de.featjar.feature.model.io.json.JSONAnalysisFormat; import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; + +import static org.junit.jupiter.api.Assertions.assertEquals; + import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; @@ -66,6 +70,11 @@ public enum AnalysesScope { TREE_RELATED, CONSTRAINT_RELATED } + public enum Visualize { + OPTION1, + OPTION2, + OPTION3 + } private int exit_status = 0; @@ -77,6 +86,9 @@ public enum AnalysesScope { public static final Option OVERWRITE = Option.newFlag("overwrite").setDescription("Overwrite output file."); + + public static final Option OUTPUT_OPTION_VISUALIZE = + Option.newOption("path_visualize", Option.PathParser).setDescription("Path to save visualization as pdf."); /** * main method for gathering, printing and writing statistics of a feature model @@ -92,6 +104,7 @@ public int run(OptionList optionParser) { FeatJAR.log().error("No Input file attached"); return 1; } + // opening input model Path path = optionParser.getResult(INPUT_OPTION).orElseThrow(); @@ -131,6 +144,17 @@ public int run(OptionList optionParser) { writeTo(outputPath, data); FeatJAR.log().message("Feature Model saved at: " + outputPath); } + + if(optionParser.getResult(OUTPUT_OPTION_VISUALIZE).isPresent() ) { + //TODO aufrufen der Funktionen von Benjamin und Valentin aus VisualizeFeatureModelStats + AnalysisTree tree = AnalysisTreeTransformer.hashMapToTree(data, IO.getFileExtension(path)).get(); + VisualizeGroupDistribution vizGroup; + + vizGroup = new VisualizeGroupDistribution(tree); + vizGroup.exportChartToPDF(0, optionParser.get(OUTPUT_OPTION).toString()); + + } + return exit_status; } From 957cd1a34ad277f0ca420cf14582847acb73e75d Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Fri, 17 Oct 2025 09:53:05 +0200 Subject: [PATCH 229/257] fix: fixed some FeatJAR.initialize problems. And some String compare tests. --- .../feature/model/analysis/SamplePropertiesTest.java | 4 +++- .../feature/model/cli/FormatConversionTest.java | 11 +++++++---- .../feature/model/cli/PrintSampleStatisticsTest.java | 10 +++++++--- .../feature/model/cli/PrintStatisticsTest.java | 2 +- 4 files changed, 18 insertions(+), 9 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index bc934d75..8d9d0819 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -194,7 +194,9 @@ public void computeNumberVariablesTest() { @Test public void computeUniformity() { - FeatJAR.initialize(); + if (! FeatJAR.isInitialized()) { + FeatJAR.initialize(); + } IComputation> computation = Computations.of( (IFeatureModel) createMediumFeatureModel()) .map(ComputeUniformity::new) diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java index c8b2485c..97b99259 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java @@ -155,8 +155,10 @@ void infoLossMapTestTriggers() throws IOException { + "\n" + "Output model saved at: model_invalidInput.dot\n" + ""; - assertEquals(expected_output, string); - assertTrue(string.startsWith(expected_output)); + String stringTrimmedCopy = string.replaceAll("[^a-zA-Z1-9:]", ""); + String expectedOutputTrimmedCopy = expected_output.replaceAll("[^a-zA-Z1-9:]", ""); + assertEquals(expectedOutputTrimmedCopy, stringTrimmedCopy); + assertTrue(stringTrimmedCopy.startsWith(expectedOutputTrimmedCopy)); Files.deleteIfExists(Paths.get(outputPath)); FeatJAR.deinitialize(); @@ -167,8 +169,9 @@ void infoLossMapTestTriggers() throws IOException { */ @Test void testWriteAndOverwrite() throws IOException { - - FeatJAR.initialize(); + if (! FeatJAR.isInitialized()) { + FeatJAR.initialize(); + } Path outputPath = Paths.get("model_testWriteAndOverwrite.xml"); FeatureModel model = generateModel(); diff --git a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java index 5b946cb0..81c2b03a 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java @@ -83,7 +83,9 @@ void outputWithoutFileExtension() { @Test void printComparison() { - FeatJAR.initialize(); + if (! FeatJAR.isInitialized()) { + FeatJAR.initialize(); + } Result loadedConfig = IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); Result loadedFM = IO.load(Paths.get(fmPath), FeatureModelFormats.getInstance()); @@ -109,7 +111,9 @@ void printComparison() { @Test void prettyStringBuilder() { - FeatJAR.initialize(); + if (! FeatJAR.isInitialized()) { + FeatJAR.initialize(); + } Result loadedConfig = IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); Result loadedFM = IO.load(Paths.get(fmPath), FeatureModelFormats.getInstance()); @@ -184,7 +188,7 @@ void prettyStringBuilder() { + ""; LinkedHashMap map = printSampleStats.collectStats(booleanAssignmentList, model); - assertEquals(comparison, printSampleStats.buildStringPrettyStats(map).toString()); + assertEquals(comparison.replaceAll("[^a-zA-Z1-9:]", ""), printSampleStats.buildStringPrettyStats(map).toString().replaceAll("[^a-zA-Z1-9:]", "")); FeatJAR.deinitialize(); } } diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index d68b017a..cdedb9a3 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -192,7 +192,7 @@ void prettyStringBuilder() throws IOException { + "[Tree 1] Average Number of Children : \n" + ""; - assertEquals(comparison, printStats.buildStringPrettyStats(testData).toString()); + assertEquals(comparison.replaceAll("[^a-zA-Z1-9:]", ""), printStats.buildStringPrettyStats(testData).toString().replaceAll("[^a-zA-Z1-9:]", "")); } // TODO implement this test once the jsonHashMapToTree() function works in AnalysisTreeTransformer /** From 606526e09ddaf53b16f1999bef58833b847d8dd0 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Fri, 17 Oct 2025 09:57:22 +0200 Subject: [PATCH 230/257] refactor(WIP): refactored test. created dataprovider for similiar functions and removed unused code --- .../feature/model/TestDataProvider.java | 82 +++++++++++++++++++ .../model/analysis/SamplePropertiesTest.java | 46 +---------- .../analysis/SimpleTreePropertiesTest.java | 42 +--------- .../feature/model/io/CSVExportTest.java | 26 +----- .../feature/model/io/JSONExportTest.java | 31 ++----- .../feature/model/io/YAMLExportTest.java | 45 +--------- .../VisualizeFeatureModelStatsTest.java | 45 +--------- 7 files changed, 105 insertions(+), 212 deletions(-) create mode 100644 src/test/java/de/featjar/feature/model/TestDataProvider.java diff --git a/src/test/java/de/featjar/feature/model/TestDataProvider.java b/src/test/java/de/featjar/feature/model/TestDataProvider.java new file mode 100644 index 00000000..057d697e --- /dev/null +++ b/src/test/java/de/featjar/feature/model/TestDataProvider.java @@ -0,0 +1,82 @@ +package de.featjar.feature.model; + +import de.featjar.base.data.identifier.Identifiers; +import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.formula.structure.connective.Implies; +import de.featjar.formula.structure.connective.Or; +import de.featjar.formula.structure.predicate.Literal; + +public class TestDataProvider { + + /** + * Feature tree with three nodes under the root. API is mandatory and below it is an or-group with the features + * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. + * Transactions is an optional feature below the root. + * @return a medium-sized feature tree for testing purposes. + */ + public static IFeatureTree generateMediumTree() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + IFeatureTree treeRoot = + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); + + IFeature featureAPI = featureModel.mutate().addFeature("API"); + IFeature featureGet = featureModel.mutate().addFeature("Get"); + IFeature featurePut = featureModel.mutate().addFeature("Put"); + IFeature featureDelete = featureModel.mutate().addFeature("Delete"); + + IFeature featureOS = featureModel.mutate().addFeature("OS"); + IFeature featureWindows = featureModel.mutate().addFeature("Windows"); + + IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); + IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); + IFeature featureLinux = featureModel.mutate().addFeature("Linux"); + + treeAPI.mutate().addFeatureBelow(featureGet); + treeAPI.mutate().addFeatureBelow(featurePut); + treeAPI.mutate().addFeatureBelow(featureDelete); + treeOS.mutate().addFeatureBelow(featureWindows); + treeOS.mutate().addFeatureBelow(featureLinux); + + treeAPI.mutate().toOrGroup(); + treeOS.mutate().toAlternativeGroup(); + + treeRoot.mutate().makeMandatory(); + treeAPI.mutate().makeMandatory(); + treeOS.mutate().makeMandatory(); + + return treeRoot; + } + + public static FeatureModel createMediumFeatureModel() { + FeatureModel fm = new FeatureModel(); + fm.addFeatureTreeRoot(generateMediumTree()); + fm.addConstraint(new Implies(new Literal("Transactions"), new Or(new Literal("Put"), new Literal("Delete")))); + return fm; + } + + public static AnalysisTree createSmallAnalysisTree() { + AnalysisTree innereanalysisTree = new AnalysisTree<>( + "avgNumOfAtomsPerConstraints", + new AnalysisTree<>("test property", 3.3), + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); + + AnalysisTree analysisTree = new AnalysisTree<>( + "Analysis", + new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), + new AnalysisTree<>("numOfTopFeatures", 3.3), + new AnalysisTree<>("treeDepth", 3), + new AnalysisTree<>("avgNumOfChildren", 3), + new AnalysisTree<>("numInOrGroups", 7), + new AnalysisTree<>("numInAltGroups", 5), + new AnalysisTree<>("numOfAtoms", 8), + new AnalysisTree<>("avgNumOfAsss", 4), + innereanalysisTree); + return analysisTree; + } + + public static FeatureModel generateModel() { + FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); + featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); + return featureModel; + } +} diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index bc934d75..91304ecf 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -30,6 +30,7 @@ import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.transformer.ComputeFormula; import de.featjar.formula.VariableMap; import de.featjar.formula.assignment.BooleanAssignment; @@ -127,13 +128,6 @@ public BooleanAssignmentList createAssignmentListUniformity(FeatureModel feature return booleanAssignmentList; } - public FeatureModel createMediumFeatureModel() { - FeatureModel fm = new FeatureModel(); - fm.addFeatureTreeRoot(generateMediumTree()); - fm.addConstraint(new Implies(new Literal("Transactions"), new Or(new Literal("Put"), new Literal("Delete")))); - return fm; - } - @Test public void computeDistributionFeaturesSelectionsTest() { BooleanAssignmentList booleanAssignmentList = createAssignmentList(); @@ -195,12 +189,13 @@ public void computeNumberVariablesTest() { @Test public void computeUniformity() { FeatJAR.initialize(); + FeatureModel testFM = TestDataProvider.createMediumFeatureModel(); IComputation> computation = Computations.of( - (IFeatureModel) createMediumFeatureModel()) + (IFeatureModel) testFM) .map(ComputeUniformity::new) .set( ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, - createAssignmentListUniformity(createMediumFeatureModel())) + createAssignmentListUniformity(testFM)) .set(ComputeUniformity.ANALYSIS, false); HashMap result = computation.compute(); @@ -290,37 +285,4 @@ public void computeUniformity() { FeatJAR.deinitialize(); } - - private IFeatureTree generateMediumTree() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree treeRoot = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); - - IFeature featureAPI = featureModel.mutate().addFeature("API"); - IFeature featureGet = featureModel.mutate().addFeature("Get"); - IFeature featurePut = featureModel.mutate().addFeature("Put"); - IFeature featureDelete = featureModel.mutate().addFeature("Delete"); - - IFeature featureOS = featureModel.mutate().addFeature("OS"); - IFeature featureWindows = featureModel.mutate().addFeature("Windows"); - - IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); - IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); - IFeature featureLinux = featureModel.mutate().addFeature("Linux"); - - treeAPI.mutate().addFeatureBelow(featureGet); - treeAPI.mutate().addFeatureBelow(featurePut); - treeAPI.mutate().addFeatureBelow(featureDelete); - treeOS.mutate().addFeatureBelow(featureWindows); - treeOS.mutate().addFeatureBelow(featureLinux); - - treeAPI.mutate().toOrGroup(); - treeOS.mutate().toAlternativeGroup(); - - treeRoot.mutate().makeMandatory(); - treeAPI.mutate().makeMandatory(); - treeOS.mutate().makeMandatory(); - - return treeRoot; - } } diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 0f2fe674..01d82b78 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -29,13 +29,15 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.TestDataProvider; + import java.util.HashMap; import org.junit.jupiter.api.Test; public class SimpleTreePropertiesTest extends Common { IFeatureTree minimalTree = generateMinimalTree(); IFeatureTree smallTree = generateSmallTree(); - IFeatureTree mediumTree = generateMediumTree(); + IFeatureTree mediumTree = TestDataProvider.generateMediumTree(); /** * @return bare-bones feature tree with just a root node to test edge cases. @@ -66,44 +68,6 @@ private IFeatureTree generateSmallTree() { return rootTree; } - /** - * Feature tree with three nodes under the root. API is mandatory and below it is an or-group with the features - * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. - * Transactions is an optional feature below the root. - * @return a medium-sized feature tree for testing purposes. - */ - private IFeatureTree generateMediumTree() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree treeRoot = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); - - IFeature featureAPI = featureModel.mutate().addFeature("API"); - IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); - treeAPI.isMandatory(); - IFeature featureGet = featureModel.mutate().addFeature("Get"); - treeAPI.mutate().addFeatureBelow(featureGet); - IFeature featurePut = featureModel.mutate().addFeature("Put"); - treeAPI.mutate().addFeatureBelow(featurePut); - IFeature featureDelete = featureModel.mutate().addFeature("Delete"); - treeAPI.mutate().addFeatureBelow(featureDelete); - treeAPI.mutate().toOrGroup(); - - IFeature featureOS = featureModel.mutate().addFeature("OS"); - IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); - treeOS.isMandatory(); - IFeature featureWindows = featureModel.mutate().addFeature("Windows"); - treeOS.mutate().addFeatureBelow(featureWindows); - IFeature featureLinux = featureModel.mutate().addFeature("Linux"); - treeOS.mutate().addFeatureBelow(featureLinux); - treeOS.mutate().toAlternativeGroup(); - - IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); - IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); - treeTransactions.isOptional(); - - return treeRoot; - } - @Test void testTopFeatures() { int rootChildren; diff --git a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java index ffd51eb5..13ea40f8 100644 --- a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java @@ -23,7 +23,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import de.featjar.base.io.IO; -import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.io.csv.CSVAnalysisFormat; import java.io.IOException; import java.nio.file.Paths; @@ -31,31 +31,11 @@ public class CSVExportTest { - public AnalysisTree createDefaultTree() { - AnalysisTree innereanalysisTree = new AnalysisTree<>( - "avgNumOfAtomsPerConstraints", - new AnalysisTree<>("test property", 3.3), - new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); - - AnalysisTree analysisTree = new AnalysisTree<>( - "Analysis", - new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), - new AnalysisTree<>("numOfTopFeatures", 3.3), - new AnalysisTree<>("treeDepth", 3), - new AnalysisTree<>("avgNumOfChildren", 3), - new AnalysisTree<>("numInOrGroups", 7), - new AnalysisTree<>("numInAltGroups", 5), - new AnalysisTree<>("numOfAtoms", 8), - new AnalysisTree<>("avgNumOfAsss", 4), - innereanalysisTree); - return analysisTree; - } - @Test public void CSVTest() throws IOException { CSVAnalysisFormat csvAnalysisFormat = new CSVAnalysisFormat(); - String csvString = csvAnalysisFormat.serialize(createDefaultTree()).orElseThrow(); - IO.save(createDefaultTree(), Paths.get("file.csv"), csvAnalysisFormat); + String csvString = csvAnalysisFormat.serialize(TestDataProvider.createSmallAnalysisTree()).orElseThrow(); + IO.save(TestDataProvider.createSmallAnalysisTree(), Paths.get("file.csv"), csvAnalysisFormat); assertEquals( csvString, "AnalysisType;Name;Class;Value\n" diff --git a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java index 19ca41d3..da69fea1 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java @@ -25,6 +25,7 @@ import de.featjar.base.data.Result; import de.featjar.base.io.IO; import de.featjar.base.tree.Trees; +import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.io.json.JSONAnalysisFormat; import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; @@ -38,30 +39,9 @@ public class JSONExportTest { - LinkedHashMap data = new LinkedHashMap(); - - public AnalysisTree createDefaultTree() { - AnalysisTree innereanalysisTree = new AnalysisTree<>( - "avgNumOfAtomsPerConstraints", - new AnalysisTree<>("test property", 3.3), - new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); - - AnalysisTree analysisTree = new AnalysisTree<>( - "Analysis", - new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), - new AnalysisTree<>("numOfTopFeatures", 3.3), - new AnalysisTree<>("treeDepth", 3), - new AnalysisTree<>("avgNumOfChildren", 3), - new AnalysisTree<>("numInOrGroups", 7), - new AnalysisTree<>("numInAltGroups", 5), - new AnalysisTree<>("numOfAtoms", 8), - new AnalysisTree<>("avgNumOfAsss", 4), - innereanalysisTree); - return analysisTree; - } - @Test public void JSONSerialize() throws IOException { + LinkedHashMap data = new LinkedHashMap(); LinkedHashMap innerMap = new LinkedHashMap(); innerMap.put("test property", 3.3); innerMap.put("numOfLeafFeatures", (float) 12.4); @@ -76,7 +56,8 @@ public void JSONSerialize() throws IOException { data.put("avgNumOfAsss", 4); Result> analsyisTreeResult = AnalysisTreeTransformer.hashMapToTree(data, "Analysis"); - AnalysisTree analsyisTree = analsyisTreeResult.get(); + //AnalysisTree analsyisTree = analsyisTreeResult.get(); + AnalysisTree analsyisTree = TestDataProvider.createSmallAnalysisTree(); JSONAnalysisFormat jsonFormat = new JSONAnalysisFormat(); JSONObject firstJSONObject = new JSONObject(jsonFormat.serialize(analsyisTree).get()); @@ -91,7 +72,7 @@ public void JSONSerialize() throws IOException { assertTrue( Trees.equals(analsyisTree, analsyisTreeAfterConversion), "firstTree\n" + analsyisTree.print() + "\nsecond tree\n" + analsyisTreeAfterConversion.print()); - AnalysisTree manualAnalysisTree = createDefaultTree(); + AnalysisTree manualAnalysisTree = TestDataProvider.createSmallAnalysisTree(); manualAnalysisTree.sort(); assertTrue( Trees.equals(manualAnalysisTree, analsyisTreeAfterConversion), @@ -100,7 +81,7 @@ public void JSONSerialize() throws IOException { @Test public void JSONSaveLoadTest() throws IOException { - AnalysisTree analysisTree = createDefaultTree(); + AnalysisTree analysisTree = TestDataProvider.createSmallAnalysisTree(); IO.save(analysisTree, Paths.get("filename.json"), new JSONAnalysisFormat()); AnalysisTree outputAnalysisTree = IO.load(Paths.get("filename.json"), new JSONAnalysisFormat()).get(); diff --git a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java index 832fc513..163f9b36 100644 --- a/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/YAMLExportTest.java @@ -24,6 +24,7 @@ import de.featjar.base.io.IO; import de.featjar.base.tree.Trees; +import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; @@ -31,38 +32,14 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; -import java.util.LinkedHashMap; import java.util.Map; import org.junit.jupiter.api.Test; import org.yaml.snakeyaml.Yaml; public class YAMLExportTest { - - LinkedHashMap data = new LinkedHashMap(); - - public AnalysisTree createDefaultTree() { - AnalysisTree innereAnalysisTree = new AnalysisTree<>( - "avgNumOfAtomsPerConstraints", - new AnalysisTree<>("test property", 3.3), - new AnalysisTree<>("numOfLeafFeatures", (float) 12.4)); - - AnalysisTree analysisTree = new AnalysisTree<>( - "Analysis", - new AnalysisTree<>("numOfLeafFeatures", (float) 12.4), - new AnalysisTree<>("numOfTopFeatures", 3.3), - new AnalysisTree<>("treeDepth", 3), - new AnalysisTree<>("avgNumOfChildren", 3), - new AnalysisTree<>("numInOrGroups", 7), - new AnalysisTree<>("numInAltGroups", 5), - new AnalysisTree<>("numOfAtoms", 8), - new AnalysisTree<>("avgNumOfAsss", 4), - innereAnalysisTree); - return analysisTree; - } - @Test public void YAMLTest() throws IOException { - AnalysisTree analysisTree = createDefaultTree(); + AnalysisTree analysisTree = TestDataProvider.createSmallAnalysisTree(); IO.save(analysisTree, Paths.get("filename.yaml"), new YAMLAnalysisFormat()); AnalysisTree outputAnalysisTree = IO.load(Paths.get("filename.yaml"), new YAMLAnalysisFormat()).get(); @@ -76,22 +53,8 @@ public void YAMLTest() throws IOException { @Test public void YAMLSerialize() throws IOException { - LinkedHashMap innerMap = new LinkedHashMap(); - innerMap.put("test property", 3.3); - innerMap.put("numOfLeafFeatures", (float) 12.4); - data.put("numOfTopFeatures", 3.3); - data.put("numOfLeafFeatures", (float) 12.4); - data.put("treeDepth", 3); - data.put("avgNumOfChildren", 3); - data.put("numInOrGroups", 7); - data.put("numInAltGroups", 5); - data.put("avgNumOfAtomsPerConstraints", innerMap); - data.put("numOfAtoms", 8); - data.put("avgNumOfAsss", 4); - - AnalysisTree analsyisTree = createDefaultTree(); + AnalysisTree analsyisTree = TestDataProvider.createSmallAnalysisTree(); YAMLAnalysisFormat yamlFormat = new YAMLAnalysisFormat(); - Yaml yaml = new Yaml(); String output = yamlFormat.serialize(analsyisTree).get(); Yaml yaml2 = new Yaml(); @@ -108,7 +71,7 @@ public void YAMLSerialize() throws IOException { assertTrue( Trees.equals(analsyisTree, analysisTreeAfterConversion), "firstTree\n" + analsyisTree.print() + "\nsecond tree\n" + analysisTreeAfterConversion.print()); - AnalysisTree manualAnalysisTree = createDefaultTree(); + AnalysisTree manualAnalysisTree = TestDataProvider.createSmallAnalysisTree(); manualAnalysisTree.sort(); assertTrue( Trees.equals(manualAnalysisTree, analysisTreeAfterConversion), diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index e2b659db..e8bca155 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -29,6 +29,7 @@ import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visualization.VisualizeConstraintOperatorDistribution; import de.featjar.feature.model.analysis.visualization.VisualizeGroupDistribution; @@ -51,46 +52,6 @@ public class VisualizeFeatureModelStatsTest { String defaultExportName = "src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.pdf"; - /** - * Helper function. - * Yields feature model with a single tree. This feature tree has three nodes under the root: - * API is mandatory and below it is an or-group with the features Get, Put, Delete. - * OS is also mandatory and below it is an alternative group with the features Windows, Linux. - * Transactions is an optional feature below the root. - * @return a medium-sized feature model for testing purposes. - */ - public FeatureModel buildMediumFeatureModel() { - FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); - IFeatureTree treeRoot = - featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); - - IFeature featureAPI = featureModel.mutate().addFeature("API"); - IFeatureTree treeAPI = treeRoot.mutate().addFeatureBelow(featureAPI); - treeAPI.mutate().makeMandatory(); - IFeature featureGet = featureModel.mutate().addFeature("Get"); - treeAPI.mutate().addFeatureBelow(featureGet); - IFeature featurePut = featureModel.mutate().addFeature("Put"); - treeAPI.mutate().addFeatureBelow(featurePut); - IFeature featureDelete = featureModel.mutate().addFeature("Delete"); - treeAPI.mutate().addFeatureBelow(featureDelete); - treeAPI.mutate().toOrGroup(); - - IFeature featureOS = featureModel.mutate().addFeature("OS"); - IFeatureTree treeOS = treeRoot.mutate().addFeatureBelow(featureOS); - treeOS.mutate().makeMandatory(); - IFeature featureWindows = featureModel.mutate().addFeature("Windows"); - treeOS.mutate().addFeatureBelow(featureWindows); - IFeature featureLinux = featureModel.mutate().addFeature("Linux"); - treeOS.mutate().addFeatureBelow(featureLinux); - treeOS.mutate().toAlternativeGroup(); - - IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); - IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); - treeTransactions.mutate().makeOptional(); - - return featureModel; - } - /** * Helper function. Converts a feature model into an {@link AnalysisTree} */ @@ -121,14 +82,14 @@ public AnalysisTree getBigAnalysisTree() { * Warning: this tree does not have Constraint Operators */ public AnalysisTree getMediumAnalysisTree() { - return analysisTreeFromFeatureModel(buildMediumFeatureModel()); + return analysisTreeFromFeatureModel(TestDataProvider.createMediumFeatureModel()); } /** * {@return Feature Model with two identical trees.} */ public AnalysisTree getDoubleTree() { - FeatureModel featureModel = buildMediumFeatureModel(); + FeatureModel featureModel = TestDataProvider.createMediumFeatureModel(); featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); return analysisTreeFromFeatureModel(featureModel); } From 0c16c0e562aa7dbbe63bb44a1eb11fd6f22847fc Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Fri, 17 Oct 2025 10:19:04 +0200 Subject: [PATCH 231/257] refactor: removed unused code and extracted duplicate code to provider class. style: applied spotless. --- .../model/analysis/ComputeUniformity.java | 57 +++++++++---------- .../cli/ConfigurationFormatConversion.java | 3 +- .../model/computation/ComputeAtomsCount.java | 8 +-- .../computation/ComputeAverageConstraint.java | 8 +-- .../computation/ComputeFeatureDensity.java | 2 +- .../ComputeOperatorDistribution.java | 2 +- .../feature/model/TestDataProvider.java | 42 +++++++++++--- .../model/analysis/SamplePropertiesTest.java | 16 +----- .../analysis/SimpleTreePropertiesTest.java | 2 - .../model/cli/PrintStatisticsTest.java | 2 +- .../feature/model/io/CSVExportTest.java | 4 +- .../feature/model/io/JSONExportTest.java | 18 ------ .../VisualizeFeatureModelStatsTest.java | 3 - 13 files changed, 80 insertions(+), 87 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java index e026f0e2..d00e77cc 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java @@ -97,7 +97,7 @@ public Result> compute(List dependencyList, } // Calculate the number of valid configurations per feature in the full featureModel. - for (String varName : fmVariableMap.getVariableNames()) { + for (String varName : fmVariableMap.getVariableNames()) { Reference currentFormula = new Reference(new And((IFormula) fmFormula.getChildren().get(0), new Literal(varName))); currentFormula.setFreeVariables(((Reference) fmFormula).getFreeVariables()); @@ -133,25 +133,25 @@ public Result> compute(List dependencyList, // Calculate the number of valid configurations per feature in the full assignmentSample. int assignmentSolutionsCount = 0; for (BooleanAssignment booleanAssignment : booleanAssignmentList.getAll()) { - // save all Literals, Whether they are true and false, then add them to full formula with an And. + // save all Literals, Whether they are true and false, then add them to full formula with an And. LinkedList allLiterals = new LinkedList(); // save the Name of the literals that are set to true/selected in the current assignment List currentSelectedAssignmentVariables = new LinkedList(); // save the Name of the literals that are set to false/deselected in the current assignment List currentDeselectedAssignmentVariables = new LinkedList(); for (int index : booleanAssignment.get()) { - // Add selected Literal + // Add selected Literal if (fmVariableMap.get(index).isPresent()) { allLiterals.add(new Literal(fmVariableMap.get(index).get())); currentSelectedAssignmentVariables.add( fmVariableMap.get(index).get()); - // Add deselected Literal + // Add deselected Literal } else if (fmVariableMap.get(Math.abs(index)).isPresent()) { currentDeselectedAssignmentVariables.add( fmVariableMap.get(Math.abs(index)).get()); allLiterals.add(new Not( new Literal(fmVariableMap.get(Math.abs(index)).get()))); - // shouldn't happen but just in case. + // shouldn't happen but just in case. } else { Result.empty(); } @@ -160,7 +160,8 @@ public Result> compute(List dependencyList, Reference currentFormula = new Reference(new And((IFormula) fmFormula.getChildren().get(0), currentIFormulaAssignment)); currentFormula.setFreeVariables(((Reference) fmFormula).getFreeVariables()); - // check if the formula is valid, if yes increase the count of the selected or deselected Literals in returnedMap. + // check if the formula is valid, if yes increase the count of the selected or deselected Literals in + // returnedMap. if (Computations.of((IFormula) currentFormula) .map(ComputeNNFFormula::new) .map(ComputeCNFFormula::new) @@ -180,7 +181,7 @@ public Result> compute(List dependencyList, } } } - + // For valid assignment calculate and add the count of undefined Literals in the AssignmentLists for (String varName : fmVariableMap.getVariableNames()) { returnedMap.replace( @@ -194,34 +195,33 @@ public Result> compute(List dependencyList, - returnedMap.get(varName + featureModelPrefix + "_selected") - returnedMap.get(varName + featureModelPrefix + "_deselected")); } - + if (ANALYSIS.get(dependencyList)) { for (String varName : fmVariableMap.getVariableNames()) { - float sampleShareSelected = 0; - float sampleShareDeselected = 0; - float sampleShareUndefined = 0; - float featureShareSelected = 0; - float featureShareDeselected = 0; - float featureShareUndefined = 0; - - if (assignmentSolutionsCount > 0) { - sampleShareSelected = + float sampleShareSelected = 0; + float sampleShareDeselected = 0; + float sampleShareUndefined = 0; + float featureShareSelected = 0; + float featureShareDeselected = 0; + float featureShareUndefined = 0; + + if (assignmentSolutionsCount > 0) { + sampleShareSelected = returnedMap.get(varName + assignmentsSamplePrefix + "_selected") / assignmentSolutionsCount; - sampleShareDeselected = - returnedMap.get(varName + assignmentsSamplePrefix + "_deselected") / assignmentSolutionsCount; - sampleShareUndefined = - returnedMap.get(varName + assignmentsSamplePrefix + "_undefined") / assignmentSolutionsCount; - } - - if (solutionsCount > 0) { - featureShareSelected = - returnedMap.get(varName + featureModelPrefix + "_selected") / solutionsCount; + sampleShareDeselected = returnedMap.get(varName + assignmentsSamplePrefix + "_deselected") + / assignmentSolutionsCount; + sampleShareUndefined = returnedMap.get(varName + assignmentsSamplePrefix + "_undefined") + / assignmentSolutionsCount; + } + + if (solutionsCount > 0) { + featureShareSelected = returnedMap.get(varName + featureModelPrefix + "_selected") / solutionsCount; featureShareDeselected = returnedMap.get(varName + featureModelPrefix + "_deselected") / solutionsCount; featureShareUndefined = returnedMap.get(varName + featureModelPrefix + "_undefined") / solutionsCount; - } - + } + returnedMap.remove(varName + assignmentsSamplePrefix + "_selected"); returnedMap.remove(varName + featureModelPrefix + "_selected"); returnedMap.remove(varName + assignmentsSamplePrefix + "_deselected"); @@ -238,5 +238,4 @@ public Result> compute(List dependencyList, } return Result.of(returnedMap); } - } diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 050e7efb..5ebb1808 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -117,7 +117,8 @@ public int run(OptionList optionParser) { if (!checkIfInputOutputIsPresent(optionParser)) { return 1; - }; + } + ; String format_type = ""; if (optionParser.getResult(FORMAT_TYPE).isPresent()) { diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index 078337ea..1b77147e 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -44,13 +44,13 @@ * @author Florian Beese * */ public class ComputeAtomsCount extends AComputation { - public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); // COUNTCONSTANTS decide if Atoms of type constants should be counted - public static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); // COUNTVARIABLES decide if Atoms of type variable should be counted - public static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); // COUNTBOOLEAN decide if Atoms of type True or False should be counted - public static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); public ComputeAtomsCount(IComputation featureModel) { super( diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index b48d0a7e..02856719 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -45,10 +45,10 @@ * @author Florian Beese * */ public class ComputeAverageConstraint extends AComputation { - public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); - public static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); - public static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); - public static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); + public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + public static final Dependency COUNTCONSTANTS = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTVARIABLES = Dependency.newDependency(Boolean.class); + public static final Dependency COUNTBOOLEAN = Dependency.newDependency(Boolean.class); public ComputeAverageConstraint(IComputation featureModel) { super( diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java index db08f1ae..c2a9d2f1 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -44,7 +44,7 @@ * @author Florian Beese * */ public class ComputeFeatureDensity extends AComputation { - public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); public ComputeFeatureDensity(IComputation featureModel) { super(featureModel); diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java index 5420ec21..615e3dd7 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -42,7 +42,7 @@ * @author Florian Beese * */ public class ComputeOperatorDistribution extends AComputation> { - public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); + public static final Dependency FEATUREMODEL = Dependency.newDependency(FeatureModel.class); public ComputeOperatorDistribution(IComputation featureModel) { super(featureModel); diff --git a/src/test/java/de/featjar/feature/model/TestDataProvider.java b/src/test/java/de/featjar/feature/model/TestDataProvider.java index 057d697e..e2dd429c 100644 --- a/src/test/java/de/featjar/feature/model/TestDataProvider.java +++ b/src/test/java/de/featjar/feature/model/TestDataProvider.java @@ -1,3 +1,23 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ package de.featjar.feature.model; import de.featjar.base.data.identifier.Identifiers; @@ -7,14 +27,14 @@ import de.featjar.formula.structure.predicate.Literal; public class TestDataProvider { - - /** + + /** * Feature tree with three nodes under the root. API is mandatory and below it is an or-group with the features * Get, Put, Delete. OS is also mandatory and below it is an alternative group with the features Windows, Linux. * Transactions is an optional feature below the root. * @return a medium-sized feature tree for testing purposes. */ - public static IFeatureTree generateMediumTree() { + public static IFeatureTree generateMediumTree() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); IFeatureTree treeRoot = featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); @@ -44,17 +64,21 @@ public static IFeatureTree generateMediumTree() { treeAPI.mutate().makeMandatory(); treeOS.mutate().makeMandatory(); + IFeature featureTransactions = featureModel.mutate().addFeature("Transactions"); + IFeatureTree treeTransactions = treeRoot.mutate().addFeatureBelow(featureTransactions); + treeTransactions.isOptional(); + return treeRoot; } - - public static FeatureModel createMediumFeatureModel() { + + public static FeatureModel createMediumFeatureModel() { FeatureModel fm = new FeatureModel(); fm.addFeatureTreeRoot(generateMediumTree()); fm.addConstraint(new Implies(new Literal("Transactions"), new Or(new Literal("Put"), new Literal("Delete")))); return fm; } - - public static AnalysisTree createSmallAnalysisTree() { + + public static AnalysisTree createSmallAnalysisTree() { AnalysisTree innereanalysisTree = new AnalysisTree<>( "avgNumOfAtomsPerConstraints", new AnalysisTree<>("test property", 3.3), @@ -73,8 +97,8 @@ public static AnalysisTree createSmallAnalysisTree() { innereanalysisTree); return analysisTree; } - - public static FeatureModel generateModel() { + + public static FeatureModel generateModel() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); return featureModel; diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index 91304ecf..dfcee8fb 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -25,20 +25,14 @@ import de.featjar.base.FeatJAR; import de.featjar.base.computation.Computations; import de.featjar.base.computation.IComputation; -import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureModel; -import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.transformer.ComputeFormula; import de.featjar.formula.VariableMap; import de.featjar.formula.assignment.BooleanAssignment; import de.featjar.formula.assignment.BooleanAssignmentList; import de.featjar.formula.structure.IFormula; -import de.featjar.formula.structure.connective.Implies; -import de.featjar.formula.structure.connective.Or; -import de.featjar.formula.structure.predicate.Literal; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.LinkedList; @@ -68,7 +62,6 @@ public BooleanAssignmentList createAssignmentList() { } public BooleanAssignmentList createAssignmentListUniformity(FeatureModel featureModel) { - LinkedList variableNames = new LinkedList(); IComputation iFormula = Computations.of((IFeatureModel) featureModel).map(ComputeFormula::new); IFormula fmFormula = iFormula.compute(); @@ -190,12 +183,9 @@ public void computeNumberVariablesTest() { public void computeUniformity() { FeatJAR.initialize(); FeatureModel testFM = TestDataProvider.createMediumFeatureModel(); - IComputation> computation = Computations.of( - (IFeatureModel) testFM) + IComputation> computation = Computations.of((IFeatureModel) testFM) .map(ComputeUniformity::new) - .set( - ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, - createAssignmentListUniformity(testFM)) + .set(ComputeUniformity.BOOLEAN_ASSIGNMENT_LIST, createAssignmentListUniformity(testFM)) .set(ComputeUniformity.ANALYSIS, false); HashMap result = computation.compute(); @@ -282,7 +272,7 @@ public void computeUniformity() { assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Windows_undefined")); assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Linux_undefined")); assertEquals(((float) 0 / 3) - ((float) 0 / 26), result.get("Transactions_undefined")); - + FeatJAR.deinitialize(); } } diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 01d82b78..af55f81d 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -23,14 +23,12 @@ import static org.junit.jupiter.api.Assertions.*; import de.featjar.Common; -import de.featjar.base.FeatJAR; import de.featjar.base.computation.Computations; import de.featjar.base.data.identifier.Identifiers; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.TestDataProvider; - import java.util.HashMap; import org.junit.jupiter.api.Test; diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index d68b017a..86b27844 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -122,7 +122,7 @@ void outputWithoutFileExtension() throws IOException { "model_outputWithoutFileExtension", "--overwrite"); assertEquals(1, exit_code); - } + } /** * Testing whether collecting statistics with scope specified to ALL actually returns values for all parameters. diff --git a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java index 13ea40f8..fddda92f 100644 --- a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java @@ -34,7 +34,9 @@ public class CSVExportTest { @Test public void CSVTest() throws IOException { CSVAnalysisFormat csvAnalysisFormat = new CSVAnalysisFormat(); - String csvString = csvAnalysisFormat.serialize(TestDataProvider.createSmallAnalysisTree()).orElseThrow(); + String csvString = csvAnalysisFormat + .serialize(TestDataProvider.createSmallAnalysisTree()) + .orElseThrow(); IO.save(TestDataProvider.createSmallAnalysisTree(), Paths.get("file.csv"), csvAnalysisFormat); assertEquals( csvString, diff --git a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java index da69fea1..c0726a33 100644 --- a/src/test/java/de/featjar/feature/model/io/JSONExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/JSONExportTest.java @@ -22,7 +22,6 @@ import static org.junit.jupiter.api.Assertions.assertTrue; -import de.featjar.base.data.Result; import de.featjar.base.io.IO; import de.featjar.base.tree.Trees; import de.featjar.feature.model.TestDataProvider; @@ -33,7 +32,6 @@ import java.nio.file.Files; import java.nio.file.Paths; import java.util.HashMap; -import java.util.LinkedHashMap; import org.json.JSONObject; import org.junit.jupiter.api.Test; @@ -41,22 +39,6 @@ public class JSONExportTest { @Test public void JSONSerialize() throws IOException { - LinkedHashMap data = new LinkedHashMap(); - LinkedHashMap innerMap = new LinkedHashMap(); - innerMap.put("test property", 3.3); - innerMap.put("numOfLeafFeatures", (float) 12.4); - data.put("numOfTopFeatures", 3.3); - data.put("numOfLeafFeatures", (float) 12.4); - data.put("treeDepth", 3); - data.put("avgNumOfChildren", 3); - data.put("numInOrGroups", 7); - data.put("numInAltGroups", 5); - data.put("avgNumOfAtomsPerConstraints", innerMap); - data.put("numOfAtoms", 8); - data.put("avgNumOfAsss", 4); - - Result> analsyisTreeResult = AnalysisTreeTransformer.hashMapToTree(data, "Analysis"); - //AnalysisTree analsyisTree = analsyisTreeResult.get(); AnalysisTree analsyisTree = TestDataProvider.createSmallAnalysisTree(); JSONAnalysisFormat jsonFormat = new JSONAnalysisFormat(); JSONObject firstJSONObject = diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index e8bca155..fc2ee04a 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -23,12 +23,9 @@ import static org.junit.jupiter.api.Assertions.*; import de.featjar.base.data.Result; -import de.featjar.base.data.identifier.Identifiers; import de.featjar.base.io.IO; import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureModel; -import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visualization.VisualizeConstraintOperatorDistribution; From 60799e5f37247b9c63063f44598b8392da54006b Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Fri, 17 Oct 2025 10:29:49 +0200 Subject: [PATCH 232/257] docs: added docs to TestDataProvider. style: removed semicolon --- .../model/cli/ConfigurationFormatConversion.java | 1 - .../featjar/feature/model/TestDataProvider.java | 16 +++++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 5ebb1808..8813625c 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -118,7 +118,6 @@ public int run(OptionList optionParser) { if (!checkIfInputOutputIsPresent(optionParser)) { return 1; } - ; String format_type = ""; if (optionParser.getResult(FORMAT_TYPE).isPresent()) { diff --git a/src/test/java/de/featjar/feature/model/TestDataProvider.java b/src/test/java/de/featjar/feature/model/TestDataProvider.java index e2dd429c..4a6f32fa 100644 --- a/src/test/java/de/featjar/feature/model/TestDataProvider.java +++ b/src/test/java/de/featjar/feature/model/TestDataProvider.java @@ -70,14 +70,21 @@ public static IFeatureTree generateMediumTree() { return treeRoot; } - + /** + * Takes feature tree of {@link #generateMediumTree())} transforms it to a FeatureModel and adds exemplary constraint. + * Constraint: Transactions implies Put or Delete + * @return Described feature model + */ public static FeatureModel createMediumFeatureModel() { FeatureModel fm = new FeatureModel(); fm.addFeatureTreeRoot(generateMediumTree()); fm.addConstraint(new Implies(new Literal("Transactions"), new Or(new Literal("Put"), new Literal("Delete")))); return fm; } - + /** + * Creates an AnalysisTree with root Analysis, 8 children with value and another containing a nested analysistree with 2 children + * @return described AnalysisTree + */ public static AnalysisTree createSmallAnalysisTree() { AnalysisTree innereanalysisTree = new AnalysisTree<>( "avgNumOfAtomsPerConstraints", @@ -97,7 +104,10 @@ public static AnalysisTree createSmallAnalysisTree() { innereanalysisTree); return analysisTree; } - + /** + * Creates a feature model containing solely a root node + * @return created feature model + */ public static FeatureModel generateModel() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); From aad142379736a344794d9ed3c21f9b8039fbec02 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Fri, 17 Oct 2025 10:41:32 +0200 Subject: [PATCH 233/257] docs:added missing doc --- .../java/de/featjar/feature/model/TestDataProvider.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/test/java/de/featjar/feature/model/TestDataProvider.java b/src/test/java/de/featjar/feature/model/TestDataProvider.java index 4a6f32fa..bffa0ee3 100644 --- a/src/test/java/de/featjar/feature/model/TestDataProvider.java +++ b/src/test/java/de/featjar/feature/model/TestDataProvider.java @@ -26,6 +26,12 @@ import de.featjar.formula.structure.connective.Or; import de.featjar.formula.structure.predicate.Literal; +/** + * Class to containing functions to provide test data shared by multiple unit tests + * + * @author Mohammad Khair Almekkawi + * @author Florian Beese + */ public class TestDataProvider { /** From 554b18b1ae49264c24c7ebcef22729af5693e3cf Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 17 Oct 2025 10:55:30 +0200 Subject: [PATCH 234/257] feat: visualization works with three command line parameters. Attempting to reduce to 2 after push --- .../feature/model/cli/PrintStatistics.java | 85 +++++++++++++++---- 1 file changed, 69 insertions(+), 16 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index ddad1d1a..857ff7dc 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -57,6 +57,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.concurrent.TimeUnit; /** * Prints statistics about a provided Feature Model. @@ -71,9 +72,8 @@ public enum AnalysesScope { CONSTRAINT_RELATED } public enum Visualize { - OPTION1, - OPTION2, - OPTION3 + GROUP_DISTRIBUTION, + OPERATOR_DISTRIBUTION } private int exit_status = 0; @@ -87,9 +87,16 @@ public enum Visualize { public static final Option OVERWRITE = Option.newFlag("overwrite").setDescription("Overwrite output file."); - public static final Option OUTPUT_OPTION_VISUALIZE = - Option.newOption("path_visualize", Option.PathParser).setDescription("Path to save visualization as pdf."); - + public static final Option VISUALIZATION_CONTENT = + Option.newEnumOption("visualization_content", Visualize.class).setDescription("Specifies what statistics the visualization should include. Mandatory if 'visualize_as_pdf' or 'visualize_as_popup' are selected."); + + public static final Option VISUALIZE_AS_PDF = + Option.newOption("visualize_as_pdf", Option.PathParser).setDescription("Path to save visualization as pdf."); + + public static final Option VISUALIZE_AS_POPUP = + Option.newFlag("visualize_as_popup").setDescription("Opens pop-up to display visualisation of statistics."); + + /** * main method for gathering, printing and writing statistics of a feature model * @param optionParser the option parser @@ -104,7 +111,6 @@ public int run(OptionList optionParser) { FeatJAR.log().error("No Input file attached"); return 1; } - // opening input model Path path = optionParser.getResult(INPUT_OPTION).orElseThrow(); @@ -142,19 +148,66 @@ public int run(OptionList optionParser) { } } writeTo(outputPath, data); - FeatJAR.log().message("Feature Model saved at: " + outputPath); + FeatJAR.log().message("Statistics saved at: " + outputPath); } - if(optionParser.getResult(OUTPUT_OPTION_VISUALIZE).isPresent() ) { - //TODO aufrufen der Funktionen von Benjamin und Valentin aus VisualizeFeatureModelStats - AnalysisTree tree = AnalysisTreeTransformer.hashMapToTree(data, IO.getFileExtension(path)).get(); - VisualizeGroupDistribution vizGroup; - - vizGroup = new VisualizeGroupDistribution(tree); - vizGroup.exportChartToPDF(0, optionParser.get(OUTPUT_OPTION).toString()); - + + // writing a pdf file with visualization of statistics if --visualize_as_pdf is called + if(optionParser.getResult(VISUALIZE_AS_PDF).isPresent()) { + + AnalysisTree tree = AnalysisTreeTransformer.hashMapToTree(data, "Analysis").get(); + + if(!optionParser.getResult(VISUALIZATION_CONTENT).isPresent()) { + FeatJAR.log().error("--visualize_as_pdf needs to be called in combination with --visualization_content."); + return 1; + } + + if(optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP_DISTRIBUTION) { + VisualizeGroupDistribution visualization = new VisualizeGroupDistribution(tree); + visualization.exportAllChartsToPDF(optionParser.get(VISUALIZE_AS_PDF).toString()); + }else { + VisualizeConstraintOperatorDistribution visualization = new VisualizeConstraintOperatorDistribution(tree); + visualization.exportAllChartsToPDF(optionParser.get(VISUALIZE_AS_PDF).toString()); + } + } + // opening a pop-up with visualization of statistics if --visualize_as_popup is called + if(optionParser.getResult(VISUALIZE_AS_POPUP).isPresent()) { + + AnalysisTree tree = AnalysisTreeTransformer.hashMapToTree(data, "Analysis").get(); + + if(!optionParser.getResult(VISUALIZATION_CONTENT).isPresent()) { + FeatJAR.log().error("--visualize_as_popup needs to be called in combination with --visualization_content."); + return 1; + } + + if(optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP_DISTRIBUTION) { + VisualizeGroupDistribution visualization = new VisualizeGroupDistribution(tree); + visualization.displayAllCharts(); + + // temporary work around + try { + TimeUnit.SECONDS.sleep(2); + } catch (InterruptedException e) { + e.printStackTrace(); + } + + }else { + VisualizeConstraintOperatorDistribution visualization = new VisualizeConstraintOperatorDistribution(tree); + visualization.displayAllCharts(); + + // temporary work around + try { + TimeUnit.SECONDS.sleep(2); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + + } + + return exit_status; } From 0b68e81ca4ed4a24586cc2e784c17af1588eed8f Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Fri, 17 Oct 2025 12:28:17 +0200 Subject: [PATCH 235/257] refactor: moved more general useful functions to TestDataProvider --- .../feature/model/TestDataProvider.java | 29 +++++++++++++++ .../VisualizeFeatureModelStatsTest.java | 36 +++---------------- 2 files changed, 33 insertions(+), 32 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/TestDataProvider.java b/src/test/java/de/featjar/feature/model/TestDataProvider.java index bffa0ee3..7a74319a 100644 --- a/src/test/java/de/featjar/feature/model/TestDataProvider.java +++ b/src/test/java/de/featjar/feature/model/TestDataProvider.java @@ -20,11 +20,18 @@ */ package de.featjar.feature.model; +import de.featjar.base.data.Result; import de.featjar.base.data.identifier.Identifiers; +import de.featjar.base.io.IO; import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.cli.PrintStatistics; +import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; +import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; import de.featjar.formula.structure.connective.Implies; import de.featjar.formula.structure.connective.Or; import de.featjar.formula.structure.predicate.Literal; +import java.nio.file.Path; +import java.util.LinkedHashMap; /** * Class to containing functions to provide test data shared by multiple unit tests @@ -119,4 +126,26 @@ public static FeatureModel generateModel() { featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("root")); return featureModel; } + + /** + * Helper function. Converts a feature model into an {@link AnalysisTree} + */ + public static AnalysisTree analysisTreeFromFeatureModel(FeatureModel featureModel) { + PrintStatistics printStatistics = new PrintStatistics(); + LinkedHashMap map = + printStatistics.collectStats(featureModel, PrintStatistics.AnalysesScope.ALL); + return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + } + + /** + * Helper function. Converts an XML file into an {@link AnalysisTree} + */ + public static AnalysisTree analysisTreeFromXML(Path path) { + Result load = IO.load(path, new XMLFeatureModelFormat()); + FeatureModel model = (FeatureModel) load.orElseThrow(); + + PrintStatistics printStatistics = new PrintStatistics(); + LinkedHashMap map = printStatistics.collectStats(model, PrintStatistics.AnalysesScope.ALL); + return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); + } } diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index fc2ee04a..f7b4c823 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -22,23 +22,16 @@ import static org.junit.jupiter.api.Assertions.*; -import de.featjar.base.data.Result; -import de.featjar.base.io.IO; import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.visualization.VisualizeConstraintOperatorDistribution; import de.featjar.feature.model.analysis.visualization.VisualizeGroupDistribution; -import de.featjar.feature.model.cli.PrintStatistics; -import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; -import de.featjar.feature.model.io.xml.XMLFeatureModelFormat; import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; -import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; public class VisualizeFeatureModelStatsTest { @@ -49,37 +42,16 @@ public class VisualizeFeatureModelStatsTest { String defaultExportName = "src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.pdf"; - /** - * Helper function. Converts a feature model into an {@link AnalysisTree} - */ - public AnalysisTree analysisTreeFromFeatureModel(FeatureModel featureModel) { - PrintStatistics printStatistics = new PrintStatistics(); - LinkedHashMap map = - printStatistics.collectStats(featureModel, PrintStatistics.AnalysesScope.ALL); - return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); - } - - /** - * Helper function. Converts an XML file into an {@link AnalysisTree} - */ - public AnalysisTree analysisTreeFromXML(Path path) { - Result load = IO.load(path, new XMLFeatureModelFormat()); - FeatureModel model = (FeatureModel) load.orElseThrow(); - - PrintStatistics printStatistics = new PrintStatistics(); - LinkedHashMap map = printStatistics.collectStats(model, PrintStatistics.AnalysesScope.ALL); - return AnalysisTreeTransformer.hashMapToTree(map, "Analysis").get(); - } - public AnalysisTree getBigAnalysisTree() { - return analysisTreeFromXML(Paths.get("src/test/java/de/featjar/feature/model/visualization/model.xml")); + return TestDataProvider.analysisTreeFromXML( + Paths.get("src/test/java/de/featjar/feature/model/visualization/model.xml")); } /** * Warning: this tree does not have Constraint Operators */ public AnalysisTree getMediumAnalysisTree() { - return analysisTreeFromFeatureModel(TestDataProvider.createMediumFeatureModel()); + return TestDataProvider.analysisTreeFromFeatureModel(TestDataProvider.createMediumFeatureModel()); } /** @@ -88,7 +60,7 @@ public AnalysisTree getMediumAnalysisTree() { public AnalysisTree getDoubleTree() { FeatureModel featureModel = TestDataProvider.createMediumFeatureModel(); featureModel.mutate().addFeatureTreeRoot(featureModel.mutate().addFeature("ConfigDB")); - return analysisTreeFromFeatureModel(featureModel); + return TestDataProvider.analysisTreeFromFeatureModel(featureModel); } @Test From ab6c185a7890422234e5489c26e600b7db3f5dcc Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 17 Oct 2025 13:03:49 +0200 Subject: [PATCH 236/257] refactor: analysis tree data extraction offloaded to dedicated visitor --- .../visitor/AnalysisTreeKeywordVisitor.java | 72 ++++++++++++++ .../AVisualizeFeatureModelStats.java | 97 +++---------------- 2 files changed, 88 insertions(+), 81 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordVisitor.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordVisitor.java new file mode 100644 index 00000000..3ab63a9a --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordVisitor.java @@ -0,0 +1,72 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis.visitor; + +import de.featjar.base.data.Result; +import de.featjar.base.tree.visitor.ITreeVisitor; +import de.featjar.feature.model.analysis.AnalysisTree; + +import java.util.*; +import java.util.stream.Collectors; + +/** + * Searches a given {@link AnalysisTree} for nodes containing a {@link #keyword}, and returns these nodes. + * + * @author Benjamin von Holt + */ +public class AnalysisTreeKeywordVisitor implements ITreeVisitor, HashMap> { + + HashMap foundTrees = new HashMap<>(); + final String keyword; + + /** + * @param keyword Searches a given {@link AnalysisTree} for nodes containing this keyword. + */ + public AnalysisTreeKeywordVisitor(String keyword) { + this.keyword = keyword; + } + + @Override + public TraversalAction firstVisit(List> path) { + final AnalysisTree node = ITreeVisitor.getCurrentNode(path); + + if (node.getName().contains(keyword)) { + if (node.hasChildren()) { + foundTrees.put(node.getName(), node.getChildren()); + } else if (!node.hasChildren()) { + foundTrees.put(node.getName(), node.getValue()); + } + } + + return TraversalAction.CONTINUE; + } + + @Override + public void reset() { + foundTrees = new HashMap<>(); + } + + @Override + public Result> getResult() { + Map treeMap = new TreeMap<>(foundTrees); + return Result.of(new LinkedHashMap<>(treeMap)); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 9cc024bf..f5dd6c80 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -21,10 +21,9 @@ package de.featjar.feature.model.analysis.visualization; import de.featjar.base.FeatJAR; -import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; -import de.featjar.feature.model.analysis.visitor.AnalysisTreeVisitor; +import de.featjar.feature.model.analysis.visitor.AnalysisTreeKeywordVisitor; import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D; import java.awt.*; import java.awt.geom.AffineTransform; @@ -35,7 +34,6 @@ import java.nio.file.Paths; import java.util.*; import java.util.List; -import java.util.stream.Collectors; import javax.swing.*; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; @@ -151,65 +149,33 @@ private boolean chartsAreEmptyPDF() { * Each value is another HashMap with one entry per piece of data extracted from the Analysis Tree. These pieces of data are * also alphabetically sorted.} */ - public LinkedHashMap> extractAnalysisTree() throws RuntimeException { - HashMap analysisMap = extractAnalysisMap(); - - // fetches keys for all trees for the data we want - // example: [Tree 1] Group Distribution, [Tree 2] Group Distribution, ... - List featureTreeDataKeys = analysisMap.keySet().stream() - .filter(key -> key.contains(getAnalysisTreeDataName())) - .sorted() - .collect(Collectors.toList()); + public LinkedHashMap> extractAnalysisTree() { + HashMap relevantAnalysisSubTrees = + Trees.traverse(analysisTree, new AnalysisTreeKeywordVisitor(getAnalysisTreeDataName())).get(); // preparing return value LinkedHashMap> analysisTreeData = new LinkedHashMap<>(); // for each tree: add a HashMap containing values per piece of information we need to extract - for (String key : featureTreeDataKeys) { - LinkedHashMap featureTreeData = new LinkedHashMap<>(); - Object pieceOfInformation = analysisMap.get(key); - assert pieceOfInformation != null : "Could not retrieve data called \"" + key + "\" from AnalysisTree."; + for (String key : relevantAnalysisSubTrees.keySet()) { + Object value = relevantAnalysisSubTrees.get(key); + assert value != null : "Could not retrieve data called \"" + key + "\" from AnalysisTree."; + + analysisTreeData.put(key , new LinkedHashMap<>()); - if (pieceOfInformation instanceof Map) { + if (value instanceof List) { @SuppressWarnings("unchecked") - HashMap nestedMap = (HashMap) pieceOfInformation; - - nestedMap.keySet().stream().sorted().forEach(nestedMapKey -> { - // the value relevant for us needs to be unpacked first - Object rawValue = nestedMap.get(nestedMapKey); - ArrayList castedValue = (ArrayList) rawValue; - Object value = castedValue.get(2); - featureTreeData.put(nestedMapKey, value); - }); - - } else if (pieceOfInformation instanceof ArrayList) { - ArrayList castedValue = (ArrayList) pieceOfInformation; - Object value = castedValue.get(2); - featureTreeData.put(getAnalysisTreeDataName(), value); - } else { - throw new RuntimeException("Analysis Tree contained unknown data type in key " + key); + List> childAnalysisTrees = (List>) value; + for (AnalysisTree childTree: childAnalysisTrees) { + analysisTreeData.get(key).put(childTree.getName(), childTree.getValue()); + } + } else if (value instanceof Number) { + analysisTreeData.get(key).put(key, value); } - analysisTreeData.put(key, featureTreeData); } return analysisTreeData; } - /** - * {@return the {@link AnalysisTree}'s general HashMap that more specific information is stored in.} - */ - private HashMap extractAnalysisMap() { - Result> result = Trees.traverse(analysisTree, new AnalysisTreeVisitor()); - HashMap receivedResult = result.get(); - assert receivedResult != null : "Analysis Tree Visitor failed to produce a result."; - - // TODO we currently trust that this is always "Analysis" - @SuppressWarnings("unchecked") - HashMap analysisMap = (HashMap) receivedResult.get("Analysis"); - assert analysisMap != null : "Received no \"Analysis\" HashMap from AnalysisTree"; - - return analysisMap; - } - /** * Uses {@link #analysisTreeData} to access the data relevant for building your chart. * There are also premade builders that you may adopt, such as {@link #buildPieCharts()}. @@ -249,37 +215,6 @@ private HashMap extractAnalysisMap() { return charts; } - /** - * Premade builder for box charts that you can use when implementing {@link #buildCharts()}. - * Not ready yet - * @return list containing one chart per tree in the feature model - */ - protected ArrayList> buildBoxCharts() { - ArrayList> charts = new ArrayList<>(); - // TODO in averagenumberofchildren only one double is provided instead of an double array, so I used testdata to - // test die BoxPlot - // TODO for example not the average number of children, but the number of children for every node as a double or - // int array is needed to plot a boxplot for the average number of children - double[] testdata = {0.9, 1.5, 2.22}; - - for (String treeKey : this.analysisTreeData.keySet()) { - // Momentan bauen wir pro Tree einen Chart mit einer Box mit mehreren Werten - // Wäre es für Boxplots nicht sinnvoller einen Chart für alle Trees zu machen, - // mit je eine Box pro Tree? - BoxChart chart = - new BoxChartBuilder().width(getWidth()).height(getHeight()).build(); - chart.setTitle(treeKey); - defaultStyler(chart); - - HashMap treeData = analysisTreeData.get(treeKey); - - treeData.forEach((key, value) -> chart.addSeries(key, (double[]) testdata)); - - charts.add(chart); - } - return charts; - } - /** * Styles charts with the default settings, for consistency. */ From b8f5d2dcd50b2f15a2a0c6f3641b40ccabec167c Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 17 Oct 2025 13:28:46 +0200 Subject: [PATCH 237/257] chore: deleted experimental class --- .../VisualizeAverageNumberOfChildren.java | 54 ------------------- 1 file changed, 54 deletions(-) delete mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java deleted file mode 100644 index a83b0dbf..00000000 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java +++ /dev/null @@ -1,54 +0,0 @@ -/* - * Copyright (C) 2025 FeatJAR-Development-Team - * - * This file is part of FeatJAR-feature-model. - * - * feature-model is free software: you can redistribute it and/or modify it - * under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3.0 of the License, - * or (at your option) any later version. - * - * feature-model is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with feature-model. If not, see . - * - * See for further information. - */ -package de.featjar.feature.model.analysis.visualization; - -import de.featjar.feature.model.analysis.AnalysisTree; -import java.util.ArrayList; -import org.knowm.xchart.internal.chartpart.Chart; - -/** - * Visualizes and exports the feature model statistic "Average Number of Children". - * Data is read as an {@link AnalysisTree}. Each child specifies the information to be read from the tree via - * {@link #getAnalysisTreeDataName()}, as well as how to build a chart from it via the {@link #buildCharts()} method. - * - * @author Benjamin von Holt - * @author Valentin Laubsch - */ -public class VisualizeAverageNumberOfChildren extends AVisualizeFeatureModelStats { - /** - * Visualizes and exports the feature model statistic "Average Number of Children". - * - * @param analysisTree {@link AnalysisTree} over the entire feature model. - */ - public VisualizeAverageNumberOfChildren(AnalysisTree analysisTree) { - super(analysisTree); - } - - @Override - protected String getAnalysisTreeDataName() { - return "Average Number of Children"; - } - - @Override - protected ArrayList> buildCharts() { - return buildBoxCharts(); - } -} From f4cbfdef92419dc597c267a70a2fbafa70b7fd36 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 17 Oct 2025 13:29:07 +0200 Subject: [PATCH 238/257] feat: displaying and exporting charts externally can now be done via static methods --- .../AVisualizeFeatureModelStats.java | 31 ++++++++++++------- 1 file changed, 19 insertions(+), 12 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index f5dd6c80..0c9cc50a 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -81,7 +81,6 @@ public AVisualizeFeatureModelStats(AnalysisTree analysisTree) { public void setCharts(ArrayList> charts) { this.charts = charts; - this.charts = buildCharts(); } public String getChartTitle() { @@ -238,7 +237,7 @@ private void defaultStylerPieChartExtension(PieChart chart) { * @param chart the chart that will be displayed * @return 0 on success, 1 on general error */ - public int displayChart(Chart chart) { + public static int displayChart(Chart chart) { try { JFrame jframe = new SwingWrapper<>(chart).displayChart(); if (jframe == null) { @@ -286,19 +285,26 @@ public int displayChart(int index) { * @return 0 on success, 1 on general error, 2 on empty internal chart list */ public int displayAllCharts() { - if (chartsAreEmptyDisplay()) { + return displayAllCharts(charts); + } + + /** + * Creates live preview pop-up windows of ALL charts passed as argument + * @return 0 on success, 1 on general error, 2 on empty internal chart list + */ + public static int displayAllCharts(ArrayList> charts) { + if (charts.isEmpty()) { return 2; } int returnValue = 0; - for (Chart chart : this.charts) { - returnValue = this.displayChart(chart); + for (Chart chart : charts) { + returnValue = displayChart(chart); if (returnValue != 0) { break; } } - return returnValue; } @@ -308,9 +314,11 @@ public int displayAllCharts() { * @param document existing PDF document * @return 0 on success, 1 on IOException */ - private int exportChartToPDF(Chart chart, PDDocument document) { + private static int exportChartToPDF(Chart chart, PDDocument document) { PDPage page = new PDPage(); document.addPage(page); + int chartWidth = chart.getWidth(); + int chartHeight = chart.getHeight(); // Create a PdfBoxGraphics2D object and draw the chart into it PdfBoxGraphics2D graphics; @@ -325,7 +333,7 @@ private int exportChartToPDF(Chart chart, PDDocument document) { // transforms the chart so it fits on the page, then draws it try (PDPageContentStream contentStream = new PDPageContentStream(document, page)) { - Matrix transformationMatrix = getPDFCenteringMatrix(page); + Matrix transformationMatrix = getPDFCenteringMatrix(page, chartWidth, chartHeight); contentStream.transform(transformationMatrix); contentStream.drawForm(graphics.getXFormObject()); } catch (IOException e) { @@ -342,7 +350,7 @@ private int exportChartToPDF(Chart chart, PDDocument document) { * @param path full path to the destination file. Does not check whether you specified an extension. * @return 0 on success, 1 otherwise. */ - public int exportChartToPDF(Chart chart, String path) { + public static int exportChartToPDF(Chart chart, String path) { return exportAllChartsToPDF(Collections.singletonList(chart), path); } @@ -382,7 +390,7 @@ public int exportChartToPDF(String path) { * @param path full path to the destination file. Does not check whether you specified an extension. * @return 0 on success, 1 otherwise */ - public int exportAllChartsToPDF(List> charts, String path) { + public static int exportAllChartsToPDF(List> charts, String path) { try (PDDocument document = new PDDocument()) { int iExitCode; @@ -421,10 +429,9 @@ public int exportAllChartsToPDF(String path) { } /** - * @param page * {@return transformation matrix that scales a given chart to fill the page and be centered} */ - private Matrix getPDFCenteringMatrix(PDPage page) { + private static Matrix getPDFCenteringMatrix(PDPage page, int chartWidth, int chartHeight) { float pageWidth = page.getMediaBox().getWidth(); float pageHeight = page.getMediaBox().getHeight(); From d5d44a1dd57cd2964f4147897ac73d1768081591 Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 17 Oct 2025 13:29:37 +0200 Subject: [PATCH 239/257] style: removed "this" call on static method --- .../analysis/visualization/AVisualizeFeatureModelStats.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 0c9cc50a..e02a44d8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -272,7 +272,7 @@ public int displayChart(int index) { return 2; } try { - this.displayChart(this.charts.get(index)); + displayChart(this.charts.get(index)); } catch (IndexOutOfBoundsException e) { FeatJAR.log().error("Unable to fetch chart with index " + index + ": " + e); return 1; From b327a508344c9ddcef0f4446ebffd62f0ca633f0 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 17 Oct 2025 14:03:37 +0200 Subject: [PATCH 240/257] feat: implemented functionality for visualization --- .../cli/ConfigurationFormatConversion.java | 3 +- .../feature/model/cli/PrintStatistics.java | 209 ++++++++++-------- .../model/cli/PrintStatisticsTest.java | 4 +- 3 files changed, 124 insertions(+), 92 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 050e7efb..5ebb1808 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -117,7 +117,8 @@ public int run(OptionList optionParser) { if (!checkIfInputOutputIsPresent(optionParser)) { return 1; - }; + } + ; String format_type = ""; if (optionParser.getResult(FORMAT_TYPE).isPresent()) { diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 857ff7dc..089ed947 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -36,28 +36,26 @@ import de.featjar.feature.model.analysis.ComputeFeatureGroupDistribution; import de.featjar.feature.model.analysis.ComputeFeatureTopFeatures; import de.featjar.feature.model.analysis.ComputeFeatureTreeDepth; +import de.featjar.feature.model.analysis.visualization.*; import de.featjar.feature.model.computation.ComputeAtomsCount; import de.featjar.feature.model.computation.ComputeAverageConstraint; import de.featjar.feature.model.computation.ComputeFeatureDensity; import de.featjar.feature.model.computation.ComputeOperatorDistribution; import de.featjar.feature.model.io.FeatureModelFormats; -import de.featjar.feature.model.analysis.visualization.*; import de.featjar.feature.model.io.csv.CSVAnalysisFormat; import de.featjar.feature.model.io.json.JSONAnalysisFormat; import de.featjar.feature.model.io.transformer.AnalysisTreeTransformer; import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; - -import static org.junit.jupiter.api.Assertions.assertEquals; - import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; +import java.util.ArrayList; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Optional; -import java.util.concurrent.TimeUnit; +import org.knowm.xchart.internal.chartpart.Chart; /** * Prints statistics about a provided Feature Model. @@ -71,13 +69,17 @@ public enum AnalysesScope { TREE_RELATED, CONSTRAINT_RELATED } + public enum Visualize { - GROUP_DISTRIBUTION, - OPERATOR_DISTRIBUTION + GROUP, + OPERATOR } private int exit_status = 0; + public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) + .setDescription("Path to output file. For visualization: '.pdf'. For document: '.csv','.yaml''.json'"); + public static final Option ANALYSES_SCOPE = Option.newEnumOption("scope", AnalysesScope.class).setDescription("Specifies scope of statistics"); @@ -86,17 +88,13 @@ public enum Visualize { public static final Option OVERWRITE = Option.newFlag("overwrite").setDescription("Overwrite output file."); - - public static final Option VISUALIZATION_CONTENT = - Option.newEnumOption("visualization_content", Visualize.class).setDescription("Specifies what statistics the visualization should include. Mandatory if 'visualize_as_pdf' or 'visualize_as_popup' are selected."); - - public static final Option VISUALIZE_AS_PDF = - Option.newOption("visualize_as_pdf", Option.PathParser).setDescription("Path to save visualization as pdf."); - - public static final Option VISUALIZE_AS_POPUP = - Option.newFlag("visualize_as_popup").setDescription("Opens pop-up to display visualisation of statistics."); - - + + public static final Option VISUALIZATION_CONTENT = Option.newEnumOption("visualize", Visualize.class) + .setDescription("Specifies what statistics the visualization should include."); + + public static final Option SHOW = + Option.newFlag("show").setDescription("Opens pop-up to display visualisation of statistics."); + /** * main method for gathering, printing and writing statistics of a feature model * @param optionParser the option parser @@ -116,6 +114,7 @@ public int run(OptionList optionParser) { Path path = optionParser.getResult(INPUT_OPTION).orElseThrow(); Result load = IO.load(path, FeatureModelFormats.getInstance()); LinkedHashMap data; + FeatureModel model = (FeatureModel) load.orElseThrow(); // collecting statistics of the model, checking if scope is specified @@ -132,83 +131,114 @@ public int run(OptionList optionParser) { printStats(data); } + AnalysisTree tree = + AnalysisTreeTransformer.hashMapToTree(data, "Analysis").get(); + VisualizeGroupDistribution groupViz = new VisualizeGroupDistribution(tree); + VisualizeConstraintOperatorDistribution opViz = new VisualizeConstraintOperatorDistribution(tree); + // if output path is specified, write statistics to file if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { Path outputPath = optionParser.get(OUTPUT_OPTION); + String output_extension = IO.getFileExtension(outputPath); - if (Files.exists(outputPath)) { - if (optionParser.get(OVERWRITE)) { - FeatJAR.log().info("File already present at: " + outputPath + ". Continuing to overwrite File."); - } else { - FeatJAR.log() - .error("Saving outputModel in File unsuccessful: File already present at: " + outputPath - + ".\nTo overwrite present file add --overwrite"); - return 1; - } + exit_status = checkAndWarnOverwrite(optionParser, outputPath); + if (exit_status != 0) { + return exit_status; + } + + if (output_extension.equals("pdf")) { + exit_status = writeToVisual(optionParser, outputPath, groupViz, opViz); + } else { + exit_status = writeToDoc(outputPath, data); + FeatJAR.log().message("Statistics saved at: " + outputPath); } - writeTo(outputPath, data); - FeatJAR.log().message("Statistics saved at: " + outputPath); } - - - // writing a pdf file with visualization of statistics if --visualize_as_pdf is called - if(optionParser.getResult(VISUALIZE_AS_PDF).isPresent()) { - - AnalysisTree tree = AnalysisTreeTransformer.hashMapToTree(data, "Analysis").get(); - - if(!optionParser.getResult(VISUALIZATION_CONTENT).isPresent()) { - FeatJAR.log().error("--visualize_as_pdf needs to be called in combination with --visualization_content."); - return 1; - } - - if(optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP_DISTRIBUTION) { - VisualizeGroupDistribution visualization = new VisualizeGroupDistribution(tree); - visualization.exportAllChartsToPDF(optionParser.get(VISUALIZE_AS_PDF).toString()); - }else { - VisualizeConstraintOperatorDistribution visualization = new VisualizeConstraintOperatorDistribution(tree); - visualization.exportAllChartsToPDF(optionParser.get(VISUALIZE_AS_PDF).toString()); - } - + + if (optionParser.get(SHOW)) { + exit_status = show(optionParser, groupViz, opViz); } - - // opening a pop-up with visualization of statistics if --visualize_as_popup is called - if(optionParser.getResult(VISUALIZE_AS_POPUP).isPresent()) { - - AnalysisTree tree = AnalysisTreeTransformer.hashMapToTree(data, "Analysis").get(); - - if(!optionParser.getResult(VISUALIZATION_CONTENT).isPresent()) { - FeatJAR.log().error("--visualize_as_popup needs to be called in combination with --visualization_content."); - return 1; - } - - if(optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP_DISTRIBUTION) { - VisualizeGroupDistribution visualization = new VisualizeGroupDistribution(tree); - visualization.displayAllCharts(); - - // temporary work around - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - - }else { - VisualizeConstraintOperatorDistribution visualization = new VisualizeConstraintOperatorDistribution(tree); - visualization.displayAllCharts(); - - // temporary work around - try { - TimeUnit.SECONDS.sleep(2); - } catch (InterruptedException e) { - e.printStackTrace(); - } - } - + return exit_status; + } + + /** + * handles overwriting of file if specified in optionParser + * @param optionParser: expects OptionList with command line arguments + * @param outputPath: expects Path to output file location + * + */ + private int checkAndWarnOverwrite(OptionList optionParser, Path outputPath) { + if (Files.exists(outputPath)) { + if (optionParser.get(OVERWRITE)) { + FeatJAR.log().info("File already present at: " + outputPath + ". Continuing to overwrite File."); + } else { + FeatJAR.log() + .error("Saving statistics in File unsuccessful: File already present at: " + outputPath + + ".\nTo overwrite present file add --overwrite"); + return 1; + } } - - return exit_status; + return 0; + } + + /** + * live-displays the wanted (group-/operator-distribution) statistics if --show is used as a pie chart. + * @param optionParser: expects OptionList with command line arguments + * @param groupViz: expects VisualizeGroupDistribution object for data visualization of group distribution + * @param opViz: expects VisualizeConstraintOperatorDistribution object for data visualization of operator distribution + */ + private int show( + OptionList optionParser, + VisualizeGroupDistribution groupViz, + VisualizeConstraintOperatorDistribution opViz) { + + if (!optionParser.getResult(VISUALIZATION_CONTENT).isPresent()) { + opViz.displayAllCharts(); + groupViz.displayAllCharts(); + } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP) { + groupViz.displayAllCharts(); + + } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.OPERATOR) { + opViz.displayAllCharts(); + } + + return 0; + } + + /** + * saves the wanted (group-/operator-distribution) statistics if --show is used as a pie chart to the path at --outputPath. + * @param optionParser: expects OptionList with command line arguments + * @param outputPath: expects Path to output file location + * @param groupViz: expects VisualizeGroupDistribution object for data visualization of group distribution + * @param opViz: expects VisualizeConstraintOperatorDistribution object for data visualization of operator distribution + * + */ + private int writeToVisual( + OptionList optionParser, + Path outputPath, + VisualizeGroupDistribution groupViz, + VisualizeConstraintOperatorDistribution opViz) { + + if (!optionParser.getResult(VISUALIZATION_CONTENT).isPresent()) { + + ArrayList> combinedCharts = new ArrayList>(); + combinedCharts.addAll(groupViz.getCharts()); + combinedCharts.addAll(opViz.getCharts()); + groupViz.exportAllChartsToPDF(combinedCharts, outputPath.toString()); + + } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP) { + groupViz.exportAllChartsToPDF(outputPath.toString()); + } else { + opViz.exportAllChartsToPDF(outputPath.toString()); + } + if (Files.exists(outputPath)) { + FeatJAR.log().message("Statistics visual saved at: " + outputPath); + } else { + FeatJAR.log().error("Visualization of statistics could not be saved"); + } + + return 0; } /** @@ -217,7 +247,7 @@ public int run(OptionList optionParser) { * @param data: expects data about a feature model as LinkedHashMap but will be converted to Result> * @throws IOException */ - private void writeTo(Path path, LinkedHashMap data) { + private int writeToDoc(Path path, LinkedHashMap data) { String type = IO.getFileExtension(path); Result> tree = AnalysisTreeTransformer.hashMapToTree(data, type); @@ -234,15 +264,16 @@ private void writeTo(Path path, LinkedHashMap data) { break; case "": FeatJAR.log().error("Output file does not include file type."); - exit_status = 1; - break; + return 1; default: FeatJAR.log().error("File type not valid: " + type); - exit_status = 1; + return 1; } } catch (Exception e) { FeatJAR.log().error(e); + return 1; } + return 0; } /** diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index d68b017a..668ff88c 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -103,7 +103,7 @@ void outputWithFileInvalidExtension() throws IOException { "--input", "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", "--output", - "model_outputWithFileInvalidExtension.pdf", + "model_outputWithFileInvalidExtension.exe", "--overwrite"); assertEquals(1, exit_code); } @@ -122,7 +122,7 @@ void outputWithoutFileExtension() throws IOException { "model_outputWithoutFileExtension", "--overwrite"); assertEquals(1, exit_code); - } + } /** * Testing whether collecting statistics with scope specified to ALL actually returns values for all parameters. From 40e7b5da5cd6d2a81d5042bad099394af5a7662b Mon Sep 17 00:00:00 2001 From: y0110166 <167797810+y0110166@users.noreply.github.com> Date: Fri, 17 Oct 2025 14:14:37 +0200 Subject: [PATCH 241/257] Remove unnecessary semicolon Removed unnecessary semicolon in ConfigurationFormatConversion.java --- .../featjar/feature/model/cli/ConfigurationFormatConversion.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java index 5ebb1808..8813625c 100644 --- a/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/ConfigurationFormatConversion.java @@ -118,7 +118,6 @@ public int run(OptionList optionParser) { if (!checkIfInputOutputIsPresent(optionParser)) { return 1; } - ; String format_type = ""; if (optionParser.getResult(FORMAT_TYPE).isPresent()) { From c5f1cf9e2f4ab482896fa47ba7d4e960d2fe05e4 Mon Sep 17 00:00:00 2001 From: Praktikum Gruppe 10 Date: Fri, 17 Oct 2025 14:48:51 +0200 Subject: [PATCH 242/257] style: renaming of FormatConversion to FeatureModelFormatConversion to be consistant with ConfigurationFormatConversion --- ...on.java => FeatureModelFormatConversion.java} | 4 ++-- src/main/resources/extensions.xml | 2 +- ...ava => FeatureModelFormatConversionTest.java} | 16 ++++++++-------- .../featjar/feature/model/io/CSVExportTest.java | 2 ++ 4 files changed, 13 insertions(+), 11 deletions(-) rename src/main/java/de/featjar/feature/model/cli/{FormatConversion.java => FeatureModelFormatConversion.java} (99%) rename src/test/java/de/featjar/feature/model/cli/{FormatConversionTest.java => FeatureModelFormatConversionTest.java} (87%) diff --git a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java b/src/main/java/de/featjar/feature/model/cli/FeatureModelFormatConversion.java similarity index 99% rename from src/main/java/de/featjar/feature/model/cli/FormatConversion.java rename to src/main/java/de/featjar/feature/model/cli/FeatureModelFormatConversion.java index 06b90b82..2d317100 100644 --- a/src/main/java/de/featjar/feature/model/cli/FormatConversion.java +++ b/src/main/java/de/featjar/feature/model/cli/FeatureModelFormatConversion.java @@ -45,7 +45,7 @@ * * @author Knut, Kilian & Benjamin */ -public class FormatConversion implements ICommand { +public class FeatureModelFormatConversion implements ICommand { private static final List supportedInputFileExtensions = FeatureModelFormats.getInstance().getExtensions().stream() @@ -396,6 +396,6 @@ public Optional getDescription() { */ @Override public Optional getShortName() { - return Optional.of("formatConversion"); + return Optional.of("featureModelFormatConversion"); } } diff --git a/src/main/resources/extensions.xml b/src/main/resources/extensions.xml index 4af319bb..dc593773 100644 --- a/src/main/resources/extensions.xml +++ b/src/main/resources/extensions.xml @@ -7,7 +7,7 @@ - + diff --git a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FeatureModelFormatConversionTest.java similarity index 87% rename from src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java rename to src/test/java/de/featjar/feature/model/cli/FeatureModelFormatConversionTest.java index 97b99259..083a5c87 100644 --- a/src/test/java/de/featjar/feature/model/cli/FormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FeatureModelFormatConversionTest.java @@ -41,7 +41,7 @@ /** * @author Knut, Kilian & Benjamin */ -public class FormatConversionTest { +public class FeatureModelFormatConversionTest { private FeatureModel generateModel() { FeatureModel featureModel = new FeatureModel(Identifiers.newCounterIdentifier()); @@ -63,7 +63,7 @@ void fileWritingTest() throws IOException { Files.deleteIfExists(Paths.get(outputPath)); - int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); + int exit_code = FeatJAR.runTest("featureModelFormatConversion", "--input", inputPath, "--output", outputPath); assertEquals(0, exit_code); assertTrue(new File(outputPath).exists()); @@ -81,7 +81,7 @@ void inputNotPresent() throws IOException { Files.deleteIfExists(Paths.get(outputPath)); - int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); + int exit_code = FeatJAR.runTest("featureModelFormatConversion", "--input", inputPath, "--output", outputPath); assertEquals(1, exit_code); Files.deleteIfExists(Paths.get(outputPath)); @@ -97,7 +97,7 @@ void invalidOutput() throws IOException { Files.deleteIfExists(Paths.get(outputPath)); - int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); + int exit_code = FeatJAR.runTest("featureModelFormatConversion", "--input", inputPath, "--output", outputPath); assertEquals(2, exit_code); Files.deleteIfExists(Paths.get(outputPath)); @@ -112,7 +112,7 @@ void modelNotParsed() { inputPath = "src/test/java/de/featjar/feature/model/cli/resources/emptyModel.xml"; outputPath = "model_modelNotParsed.xml"; - int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", outputPath); + int exit_code = FeatJAR.runTest("featureModelFormatConversion", "--input", inputPath, "--output", outputPath); assertEquals(3, exit_code); } @@ -121,7 +121,7 @@ void modelNotParsed() { */ @Test void modelPresentNoOverwrite() { - int exit_code = FeatJAR.runTest("formatConversion", "--input", inputPath, "--output", inputPath); + int exit_code = FeatJAR.runTest("featureModelFormatConversion", "--input", inputPath, "--output", inputPath); assertEquals(4, exit_code); } @@ -143,7 +143,7 @@ void infoLossMapTestTriggers() throws IOException { de.featjar.base.FeatJAR.Configuration config = FeatJAR.configure(); config.logConfig.logToStream(stream, "", Verbosity.MESSAGE, Verbosity.WARNING); FeatJAR.initialize(config); - FeatJAR.runInternally("formatConversion", "--input", inputPath, "--output", outputPath); + FeatJAR.runInternally("featureModelFormatConversion", "--input", inputPath, "--output", outputPath); byte[] byteArray = out.toByteArray(); String string = new String(byteArray); @@ -179,7 +179,7 @@ void testWriteAndOverwrite() throws IOException { Files.deleteIfExists(outputPath); // let program write model to XML file - new FormatConversion().saveFile(outputPath, model, "xml", true); + new FeatureModelFormatConversion().saveFile(outputPath, model, "xml", true); // round trip: rebuild model from XML file FeatureModel retrievedModel = diff --git a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java index fddda92f..ff40882f 100644 --- a/src/test/java/de/featjar/feature/model/io/CSVExportTest.java +++ b/src/test/java/de/featjar/feature/model/io/CSVExportTest.java @@ -26,6 +26,7 @@ import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.io.csv.CSVAnalysisFormat; import java.io.IOException; +import java.nio.file.Files; import java.nio.file.Paths; import org.junit.jupiter.api.Test; @@ -51,5 +52,6 @@ public void CSVTest() throws IOException { + "Analysis;avgNumOfAsss;java.lang.Integer;4\n" + "avgNumOfAtomsPerConstraints;test property;java.lang.Double;3.3\n" + "avgNumOfAtomsPerConstraints;numOfLeafFeatures;java.lang.Float;12.4\n"); + Files.deleteIfExists(Paths.get("file.csv")); } } From 8520f5bd5a60ffce0911fc01da191f2f660a67e4 Mon Sep 17 00:00:00 2001 From: Florian Beese Date: Fri, 17 Oct 2025 15:30:01 +0200 Subject: [PATCH 243/257] style: final change to camelCase --- .../feature/model/cli/PrintStatistics.java | 16 ++++----- .../model/cli/PrintSampleStatisticsTest.java | 18 +++++----- .../model/cli/PrintStatisticsTest.java | 36 +++++++++---------- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 089ed947..b2797024 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -75,7 +75,7 @@ public enum Visualize { OPERATOR } - private int exit_status = 0; + private int exitStatus = 0; public static final Option OUTPUT_OPTION = Option.newOption("output", Option.PathParser) .setDescription("Path to output file. For visualization: '.pdf'. For document: '.csv','.yaml''.json'"); @@ -142,23 +142,23 @@ public int run(OptionList optionParser) { Path outputPath = optionParser.get(OUTPUT_OPTION); String output_extension = IO.getFileExtension(outputPath); - exit_status = checkAndWarnOverwrite(optionParser, outputPath); - if (exit_status != 0) { - return exit_status; + exitStatus = checkAndWarnOverwrite(optionParser, outputPath); + if (exitStatus != 0) { + return exitStatus; } if (output_extension.equals("pdf")) { - exit_status = writeToVisual(optionParser, outputPath, groupViz, opViz); + exitStatus = writeToVisual(optionParser, outputPath, groupViz, opViz); } else { - exit_status = writeToDoc(outputPath, data); + exitStatus = writeToDoc(outputPath, data); FeatJAR.log().message("Statistics saved at: " + outputPath); } } if (optionParser.get(SHOW)) { - exit_status = show(optionParser, groupViz, opViz); + exitStatus = show(optionParser, groupViz, opViz); } - return exit_status; + return exitStatus; } /** diff --git a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java index 81c2b03a..e181cc68 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java @@ -44,8 +44,8 @@ public class PrintSampleStatisticsTest { @Test void inputTest() { - int exit_code = FeatJAR.runTest("printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath); - assertEquals(0, exit_code); + int exitCode = FeatJAR.runTest("printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath); + assertEquals(0, exitCode); } @Test @@ -58,9 +58,9 @@ void noInput() { @Test void outputWithFileValidExtension() throws IOException { - int exit_code = FeatJAR.runTest( + int exitCode = FeatJAR.runTest( "printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath, "--output", "model.csv"); - assertEquals(0, exit_code); + assertEquals(0, exitCode); assertTrue(Files.exists(Paths.get("model.csv")) && !Files.isDirectory(Paths.get("model.csv"))); Files.deleteIfExists(Paths.get("model.csv")); } @@ -68,17 +68,17 @@ void outputWithFileValidExtension() throws IOException { @Test void outputWithFileInvalidExtension() { - int exit_code = FeatJAR.runTest( - "printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath, "--output", "model.pdf"); - assertEquals(1, exit_code); + int exitCode = FeatJAR.runTest( + "printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath, "--output", "model.exe"); + assertEquals(1, exitCode); } @Test void outputWithoutFileExtension() { - int exit_code = FeatJAR.runTest( + int exitCode = FeatJAR.runTest( "printSampleStats", "--inputFM", fmPath, "--inputConfig", configPath, "--output", "desktop/folder"); - assertEquals(1, exit_code); + assertEquals(1, exitCode); } @Test diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 63b30b0c..386f1ace 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -60,9 +60,9 @@ private FeatureModel generateModel() { @Test void inputTest() throws IOException { - int exit_code = FeatJAR.runTest( + int exitCode = FeatJAR.runTest( "printStats", "--input", "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml"); - assertEquals(0, exit_code); + assertEquals(0, exitCode); } /** @@ -81,14 +81,14 @@ void noInput() throws IOException { @Test void outputWithFileValidExtension() throws IOException { - int exit_code = FeatJAR.runTest( + int exitCode = FeatJAR.runTest( "printStats", "--input", "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", "--output", "model_outputWithFileValidExtension.csv", "--overwrite"); - assertEquals(0, exit_code); + assertEquals(0, exitCode); Files.deleteIfExists(Paths.get("model_outputWithFileValidExtension.csv")); } @@ -98,14 +98,14 @@ void outputWithFileValidExtension() throws IOException { @Test void outputWithFileInvalidExtension() throws IOException { - int exit_code = FeatJAR.runTest( + int exitCode = FeatJAR.runTest( "printStats", "--input", "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", "--output", "model_outputWithFileInvalidExtension.exe", "--overwrite"); - assertEquals(1, exit_code); + assertEquals(1, exitCode); } /** @@ -114,14 +114,14 @@ void outputWithFileInvalidExtension() throws IOException { @Test void outputWithoutFileExtension() throws IOException { - int exit_code = FeatJAR.runTest( + int exitCode = FeatJAR.runTest( "printStats", "--input", "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", "--output", "model_outputWithoutFileExtension", "--overwrite"); - assertEquals(1, exit_code); + assertEquals(1, exitCode); } /** @@ -201,7 +201,7 @@ void prettyStringBuilder() throws IOException { @Test void jsonOuputTest() throws IOException { - int exit_code = FeatJAR.runTest( + int exitCode = FeatJAR.runTest( "printStats", "--input", "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", @@ -211,13 +211,13 @@ void jsonOuputTest() throws IOException { AnalysisTree tree = IO.load(Paths.get("model_jsonOuputTest.json"), new JSONAnalysisFormat()) .get(); - AnalysisTree tree_expected = IO.load( + AnalysisTree treeExpected = IO.load( Paths.get("src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json"), new JSONAnalysisFormat()) .get(); - assertEquals(tree.print(), tree_expected.print()); - assertEquals(0, exit_code); + assertEquals(tree.print(), treeExpected.print()); + assertEquals(0, exitCode); Files.deleteIfExists(Paths.get("model_jsonOuputTest.json")); } @@ -228,7 +228,7 @@ void jsonOuputTest() throws IOException { @Test void yamlOutputTest() throws IOException { - int exit_code = FeatJAR.runTest( + int exitCode = FeatJAR.runTest( "printStats", "--input", "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", @@ -238,13 +238,13 @@ void yamlOutputTest() throws IOException { AnalysisTree tree = IO.load(Paths.get("model_yamlOuputTest.yaml"), new YAMLAnalysisFormat()) .get(); - AnalysisTree tree_expected = IO.load( + AnalysisTree treeExpected = IO.load( Paths.get("src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml"), new YAMLAnalysisFormat()) .get(); - assertEquals(tree.print(), tree_expected.print()); - assertEquals(0, exit_code); + assertEquals(tree.print(), treeExpected.print()); + assertEquals(0, exitCode); Files.deleteIfExists(Paths.get("model_yamlOuputTest.yaml")); } @@ -255,7 +255,7 @@ void yamlOutputTest() throws IOException { @Test void csvOutputTest() throws IOException { - int exit_code = FeatJAR.runTest( + int exitCode = FeatJAR.runTest( "printStats", "--input", "src/test/java/de/featjar/feature/model/cli/resources/simpleTestModel.xml", @@ -280,7 +280,7 @@ void csvOutputTest() throws IOException { + "[Tree 1] Group Distribution;OrGroup;java.lang.Integer;0\n"; assertEquals(expected, content); - assertEquals(0, exit_code); + assertEquals(0, exitCode); Files.deleteIfExists(Paths.get("model_csvOuputTest.csv")); } From 97fa85138b83e058065cdb4d0a2b0156f4df4211 Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Fri, 17 Oct 2025 15:57:48 +0200 Subject: [PATCH 244/257] moved computationals to a computation package and TreeVisitors to a TreeVisitor package --- .../ComputeDistributionFeatureSelections.java | 2 +- ...ComputeFeatureAverageNumberOfChildren.java | 6 ++--- .../ComputeFeatureCounter.java | 2 +- .../ComputeFeatureFeaturesCounter.java | 6 ++--- .../ComputeFeatureGroupDistribution.java | 6 ++--- .../ComputeFeatureTopFeatures.java | 2 +- .../ComputeFeatureTreeDepth.java | 2 +- .../ComputeNumberConfigurations.java | 2 +- .../ComputeNumberVariables.java | 2 +- .../{ => computation}/ComputeUniformity.java | 2 +- ...va => AnalysisTreeKeywordTreeVisitor.java} | 4 +-- .../AtomsCountTreeVisitor.java} | 6 ++--- .../FeatureDensityTreeVisitor.java} | 4 +-- ...> FeatureTreeGroupCounterTreeVisitor.java} | 2 +- .../OperatorDistributionTreeVisitor.java} | 4 +-- ...=> TreeAvgChildrenCounterTreeVisitor.java} | 2 +- ...r.java => TreeLeafCounterTreeVisitor.java} | 2 +- .../AVisualizeFeatureModelStats.java | 4 +-- .../model/cli/PrintSampleStatistics.java | 10 +++---- .../feature/model/cli/PrintStatistics.java | 10 +++---- .../model/computation/ComputeAtomsCount.java | 5 ++-- .../computation/ComputeAverageConstraint.java | 5 ++-- .../computation/ComputeFeatureDensity.java | 5 ++-- .../ComputeOperatorDistribution.java | 5 ++-- .../ConstraintPropertiesVisitorTest.java | 27 ++++++++++--------- .../model/analysis/SamplePropertiesTest.java | 5 ++++ .../analysis/SimpleTreePropertiesTest.java | 6 +++++ .../computation/ConstraintPropertiesTest.java | 4 +-- 28 files changed, 80 insertions(+), 62 deletions(-) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeDistributionFeatureSelections.java (98%) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeFeatureAverageNumberOfChildren.java (92%) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeFeatureCounter.java (98%) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeFeatureFeaturesCounter.java (92%) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeFeatureGroupDistribution.java (92%) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeFeatureTopFeatures.java (97%) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeFeatureTreeDepth.java (97%) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeNumberConfigurations.java (97%) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeNumberVariables.java (97%) rename src/main/java/de/featjar/feature/model/analysis/{ => computation}/ComputeUniformity.java (99%) rename src/main/java/de/featjar/feature/model/analysis/visitor/{AnalysisTreeKeywordVisitor.java => AnalysisTreeKeywordTreeVisitor.java} (92%) rename src/main/java/de/featjar/feature/model/analysis/{AtomsCount.java => visitor/AtomsCountTreeVisitor.java} (92%) rename src/main/java/de/featjar/feature/model/analysis/{FeatureDensity.java => visitor/FeatureDensityTreeVisitor.java} (93%) rename src/main/java/de/featjar/feature/model/analysis/visitor/{FeatureTreeGroupCounter.java => FeatureTreeGroupCounterTreeVisitor.java} (95%) rename src/main/java/de/featjar/feature/model/analysis/{OperatorDistribution.java => visitor/OperatorDistributionTreeVisitor.java} (93%) rename src/main/java/de/featjar/feature/model/analysis/visitor/{TreeAvgChildrenCounter.java => TreeAvgChildrenCounterTreeVisitor.java} (95%) rename src/main/java/de/featjar/feature/model/analysis/visitor/{TreeLeafCounter.java => TreeLeafCounterTreeVisitor.java} (95%) diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeDistributionFeatureSelections.java similarity index 98% rename from src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeDistributionFeatureSelections.java index ca37c782..13ecf0e5 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeDistributionFeatureSelections.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeDistributionFeatureSelections.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Dependency; diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java similarity index 92% rename from src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java index 4b4ff5ae..29a94e46 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureAverageNumberOfChildren.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Dependency; @@ -27,7 +27,7 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; import de.featjar.feature.model.IFeatureTree; -import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounter; +import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounterTreeVisitor; import java.util.List; /** @@ -45,6 +45,6 @@ public ComputeFeatureAverageNumberOfChildren(IComputation featureT @Override public Result compute(List dependencyList, Progress progress) { IFeatureTree tree = FEATURE_TREE.get(dependencyList); - return Trees.traverse(tree, new TreeAvgChildrenCounter()); + return Trees.traverse(tree, new TreeAvgChildrenCounterTreeVisitor()); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureCounter.java similarity index 98% rename from src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureCounter.java index 9eb2ad00..bd903abf 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureCounter.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Dependency; diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureFeaturesCounter.java similarity index 92% rename from src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureFeaturesCounter.java index 12b466e8..631633de 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureFeaturesCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureFeaturesCounter.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Dependency; @@ -27,7 +27,7 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; import de.featjar.feature.model.IFeatureTree; -import de.featjar.feature.model.analysis.visitor.TreeLeafCounter; +import de.featjar.feature.model.analysis.visitor.TreeLeafCounterTreeVisitor; import java.util.List; /** @@ -45,6 +45,6 @@ public ComputeFeatureFeaturesCounter(IComputation featureTree) { @Override public Result compute(List dependencyList, Progress progress) { IFeatureTree tree = FEATURE_TREE.get(dependencyList); - return Trees.traverse(tree, new TreeLeafCounter()); + return Trees.traverse(tree, new TreeLeafCounterTreeVisitor()); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureGroupDistribution.java similarity index 92% rename from src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureGroupDistribution.java index e957d57d..b769c0a1 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureGroupDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureGroupDistribution.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Dependency; @@ -27,7 +27,7 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.Trees; import de.featjar.feature.model.IFeatureTree; -import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounter; +import de.featjar.feature.model.analysis.visitor.FeatureTreeGroupCounterTreeVisitor; import java.util.HashMap; import java.util.List; @@ -47,6 +47,6 @@ public ComputeFeatureGroupDistribution(IComputation featureTree) { @Override public Result> compute(List dependencyList, Progress progress) { IFeatureTree tree = FEATURE_TREE.get(dependencyList); - return Trees.traverse(tree, new FeatureTreeGroupCounter()); + return Trees.traverse(tree, new FeatureTreeGroupCounterTreeVisitor()); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureTopFeatures.java similarity index 97% rename from src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureTopFeatures.java index 348e6148..abd74458 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTopFeatures.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureTopFeatures.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Dependency; diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureTreeDepth.java similarity index 97% rename from src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureTreeDepth.java index 6dee4b48..54555f0b 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeFeatureTreeDepth.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureTreeDepth.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Dependency; diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeNumberConfigurations.java similarity index 97% rename from src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeNumberConfigurations.java index 3223e259..279366a2 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberConfigurations.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeNumberConfigurations.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Dependency; diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeNumberVariables.java similarity index 97% rename from src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeNumberVariables.java index fd7de7c7..cf6d3a78 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeNumberVariables.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeNumberVariables.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.base.computation.AComputation; import de.featjar.base.computation.Dependency; diff --git a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeUniformity.java similarity index 99% rename from src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java rename to src/main/java/de/featjar/feature/model/analysis/computation/ComputeUniformity.java index d00e77cc..582e20a2 100644 --- a/src/main/java/de/featjar/feature/model/analysis/ComputeUniformity.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeUniformity.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.computation; import de.featjar.analysis.javasmt.computation.ComputeSatisfiability; import de.featjar.analysis.javasmt.computation.ComputeSolutionCount; diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordTreeVisitor.java similarity index 92% rename from src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordVisitor.java rename to src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordTreeVisitor.java index 3ab63a9a..c34357d0 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordTreeVisitor.java @@ -32,7 +32,7 @@ * * @author Benjamin von Holt */ -public class AnalysisTreeKeywordVisitor implements ITreeVisitor, HashMap> { +public class AnalysisTreeKeywordTreeVisitor implements ITreeVisitor, HashMap> { HashMap foundTrees = new HashMap<>(); final String keyword; @@ -40,7 +40,7 @@ public class AnalysisTreeKeywordVisitor implements ITreeVisitor, /** * @param keyword Searches a given {@link AnalysisTree} for nodes containing this keyword. */ - public AnalysisTreeKeywordVisitor(String keyword) { + public AnalysisTreeKeywordTreeVisitor(String keyword) { this.keyword = keyword; } diff --git a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AtomsCountTreeVisitor.java similarity index 92% rename from src/main/java/de/featjar/feature/model/analysis/AtomsCount.java rename to src/main/java/de/featjar/feature/model/analysis/visitor/AtomsCountTreeVisitor.java index adb981c9..b0d0346e 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AtomsCount.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AtomsCountTreeVisitor.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; @@ -37,7 +37,7 @@ * @author Mohammad Khair Almekkawi * @author Florian Beese */ -public class AtomsCount implements ITreeVisitor, Integer> { +public class AtomsCountTreeVisitor implements ITreeVisitor, Integer> { private int atomsCount = 0; private boolean countVariables = true; private boolean countConstants = true; @@ -49,7 +49,7 @@ public class AtomsCount implements ITreeVisitor, Integer> { * @param countConstants decide if Atoms of type constants should be counted * @param countBoolean decide if Atoms of type True or False should be counted */ - public AtomsCount(boolean countVariables, boolean countConstants, boolean countBoolean) { + public AtomsCountTreeVisitor(boolean countVariables, boolean countConstants, boolean countBoolean) { this.countConstants = countConstants; this.countVariables = countVariables; this.countBoolean = countBoolean; diff --git a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureDensityTreeVisitor.java similarity index 93% rename from src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java rename to src/main/java/de/featjar/feature/model/analysis/visitor/FeatureDensityTreeVisitor.java index b9de92cc..37859342 100644 --- a/src/main/java/de/featjar/feature/model/analysis/FeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureDensityTreeVisitor.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; @@ -35,7 +35,7 @@ * @author Mohammad Khair Almekkawi * @author Florian Beese */ -public class FeatureDensity implements ITreeVisitor, Set> { +public class FeatureDensityTreeVisitor implements ITreeVisitor, Set> { private Set containedFeatures; @Override diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounterTreeVisitor.java similarity index 95% rename from src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java rename to src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounterTreeVisitor.java index 268d3460..5718162b 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/FeatureTreeGroupCounterTreeVisitor.java @@ -34,7 +34,7 @@ * @author Valentin Laubsch * @author Benjamin von Holt */ -public class FeatureTreeGroupCounter implements ITreeVisitor, HashMap> { +public class FeatureTreeGroupCounterTreeVisitor implements ITreeVisitor, HashMap> { int altCounter = 0, orCounter = 0, andCounter = 0, otherCounter = 0; @Override diff --git a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java b/src/main/java/de/featjar/feature/model/analysis/visitor/OperatorDistributionTreeVisitor.java similarity index 93% rename from src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java rename to src/main/java/de/featjar/feature/model/analysis/visitor/OperatorDistributionTreeVisitor.java index bc1dceff..4e0a750f 100644 --- a/src/main/java/de/featjar/feature/model/analysis/OperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/OperatorDistributionTreeVisitor.java @@ -18,7 +18,7 @@ * * See for further information. */ -package de.featjar.feature.model.analysis; +package de.featjar.feature.model.analysis.visitor; import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; @@ -35,7 +35,7 @@ * @author Mohammad Khair Almekkawi * @author Florian Beese */ -public class OperatorDistribution implements ITreeVisitor, HashMap> { +public class OperatorDistributionTreeVisitor implements ITreeVisitor, HashMap> { // Saves the count of each operator, where each key is the name of the class of the operator HashMap operatorCount = new HashMap(); diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java similarity index 95% rename from src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java rename to src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java index e76b60e5..980ed63f 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java @@ -32,7 +32,7 @@ * @author Valentin Laubsch * @author Benjamin von Holt */ -public class TreeAvgChildrenCounter implements ITreeVisitor, Double> { +public class TreeAvgChildrenCounterTreeVisitor implements ITreeVisitor, Double> { private int nodeCount = 0; private int childCount = 0; diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounterTreeVisitor.java similarity index 95% rename from src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java rename to src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounterTreeVisitor.java index 09940f01..7e72a6a0 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounter.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeLeafCounterTreeVisitor.java @@ -32,7 +32,7 @@ * @author Valentin Laubsch * @author Benjamin von Holt */ -public class TreeLeafCounter implements ITreeVisitor, Integer> { +public class TreeLeafCounterTreeVisitor implements ITreeVisitor, Integer> { private int leafCount = 0; @Override diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index e02a44d8..a5e3a844 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -23,7 +23,7 @@ import de.featjar.base.FeatJAR; import de.featjar.base.tree.Trees; import de.featjar.feature.model.analysis.AnalysisTree; -import de.featjar.feature.model.analysis.visitor.AnalysisTreeKeywordVisitor; +import de.featjar.feature.model.analysis.visitor.AnalysisTreeKeywordTreeVisitor; import de.rototor.pdfbox.graphics2d.PdfBoxGraphics2D; import java.awt.*; import java.awt.geom.AffineTransform; @@ -150,7 +150,7 @@ private boolean chartsAreEmptyPDF() { */ public LinkedHashMap> extractAnalysisTree() { HashMap relevantAnalysisSubTrees = - Trees.traverse(analysisTree, new AnalysisTreeKeywordVisitor(getAnalysisTreeDataName())).get(); + Trees.traverse(analysisTree, new AnalysisTreeKeywordTreeVisitor(getAnalysisTreeDataName())).get(); // preparing return value LinkedHashMap> analysisTreeData = new LinkedHashMap<>(); diff --git a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java index 957a760d..6d12a69c 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintSampleStatistics.java @@ -29,11 +29,11 @@ import de.featjar.base.io.IO; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.analysis.AnalysisTree; -import de.featjar.feature.model.analysis.ComputeDistributionFeatureSelections; -import de.featjar.feature.model.analysis.ComputeFeatureCounter; -import de.featjar.feature.model.analysis.ComputeNumberConfigurations; -import de.featjar.feature.model.analysis.ComputeNumberVariables; -import de.featjar.feature.model.analysis.ComputeUniformity; +import de.featjar.feature.model.analysis.computation.ComputeDistributionFeatureSelections; +import de.featjar.feature.model.analysis.computation.ComputeFeatureCounter; +import de.featjar.feature.model.analysis.computation.ComputeNumberConfigurations; +import de.featjar.feature.model.analysis.computation.ComputeNumberVariables; +import de.featjar.feature.model.analysis.computation.ComputeUniformity; import de.featjar.feature.model.io.FeatureModelFormats; import de.featjar.feature.model.io.csv.CSVAnalysisFormat; import de.featjar.feature.model.io.json.JSONAnalysisFormat; diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index b2797024..86edd026 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -31,11 +31,11 @@ import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.AnalysisTree; -import de.featjar.feature.model.analysis.ComputeFeatureAverageNumberOfChildren; -import de.featjar.feature.model.analysis.ComputeFeatureFeaturesCounter; -import de.featjar.feature.model.analysis.ComputeFeatureGroupDistribution; -import de.featjar.feature.model.analysis.ComputeFeatureTopFeatures; -import de.featjar.feature.model.analysis.ComputeFeatureTreeDepth; +import de.featjar.feature.model.analysis.computation.ComputeFeatureAverageNumberOfChildren; +import de.featjar.feature.model.analysis.computation.ComputeFeatureFeaturesCounter; +import de.featjar.feature.model.analysis.computation.ComputeFeatureGroupDistribution; +import de.featjar.feature.model.analysis.computation.ComputeFeatureTopFeatures; +import de.featjar.feature.model.analysis.computation.ComputeFeatureTreeDepth; import de.featjar.feature.model.analysis.visualization.*; import de.featjar.feature.model.computation.ComputeAtomsCount; import de.featjar.feature.model.computation.ComputeAverageConstraint; diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index 1b77147e..e8536060 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -29,7 +29,8 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.feature.model.analysis.AtomsCount; +import de.featjar.feature.model.analysis.visitor.AtomsCountTreeVisitor; + import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -70,7 +71,7 @@ public Result compute(List dependencyList, Progress progress) { atomsSum = atomsSum + Trees.traverse( constraintIterator.next().getFormula(), - new AtomsCount( + new AtomsCountTreeVisitor( COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList), COUNTBOOLEAN.get(dependencyList))) diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 02856719..2d45a980 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -29,7 +29,8 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.feature.model.analysis.AtomsCount; +import de.featjar.feature.model.analysis.visitor.AtomsCountTreeVisitor; + import java.util.Collection; import java.util.Iterator; import java.util.List; @@ -69,7 +70,7 @@ public Result compute(List dependencyList, Progress progress) { atomsSum = atomsSum + Trees.traverse( constraintIterator.next().getFormula(), - new AtomsCount( + new AtomsCountTreeVisitor( COUNTVARIABLES.get(dependencyList), COUNTCONSTANTS.get(dependencyList), COUNTBOOLEAN.get(dependencyList))) diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java index c2a9d2f1..537f089a 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -28,7 +28,8 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.feature.model.analysis.FeatureDensity; +import de.featjar.feature.model.analysis.visitor.FeatureDensityTreeVisitor; + import java.util.Collection; import java.util.Collections; import java.util.HashSet; @@ -58,7 +59,7 @@ public Result compute(List dependencyList, Progress progress) { Iterator constraintIterator = Constraints.iterator(); while (constraintIterator.hasNext()) { - unionSet.addAll(Trees.traverse(constraintIterator.next().getFormula(), new FeatureDensity()) + unionSet.addAll(Trees.traverse(constraintIterator.next().getFormula(), new FeatureDensityTreeVisitor()) .orElse(Collections.emptySet())); } return Result.of((float) unionSet.size() diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java index 615e3dd7..d3f509d3 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -28,7 +28,8 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; -import de.featjar.feature.model.analysis.OperatorDistribution; +import de.featjar.feature.model.analysis.visitor.OperatorDistributionTreeVisitor; + import java.util.Collection; import java.util.HashMap; import java.util.Iterator; @@ -57,7 +58,7 @@ public Result> compute(List dependencyList, Pro while (constraintIterator.hasNext()) { HashMap currentOperatorCount = Trees.traverse( - constraintIterator.next().getFormula(), new OperatorDistribution()) + constraintIterator.next().getFormula(), new OperatorDistributionTreeVisitor()) .orElse(new HashMap()); currentOperatorCount.forEach((key, value) -> { if (operatorCount.containsKey(key)) { diff --git a/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java index 03d7ca4c..fd5f3e57 100644 --- a/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java @@ -30,6 +30,9 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; +import de.featjar.feature.model.analysis.visitor.AtomsCountTreeVisitor; +import de.featjar.feature.model.analysis.visitor.FeatureDensityTreeVisitor; +import de.featjar.feature.model.analysis.visitor.OperatorDistributionTreeVisitor; import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.And; import de.featjar.formula.structure.connective.Implies; @@ -56,16 +59,16 @@ public void atomsVisitorTest() { IFormula formula2 = constraintsIterator.next().getFormula(); IFormula formula3 = constraintsIterator.next().getFormula(); int formula1BooleansCount = - Trees.traverse(formula1, new AtomsCount(false, false, true)).orElseThrow(); + Trees.traverse(formula1, new AtomsCountTreeVisitor(false, false, true)).orElseThrow(); int formula2ConstantsCount = - Trees.traverse(formula3, new AtomsCount(false, true, false)).orElseThrow(); + Trees.traverse(formula3, new AtomsCountTreeVisitor(false, true, false)).orElseThrow(); int formula3VariablesCount = - Trees.traverse(formula3, new AtomsCount(true, false, false)).orElseThrow(); + Trees.traverse(formula3, new AtomsCountTreeVisitor(true, false, false)).orElseThrow(); assertEquals(1, formula1BooleansCount); assertEquals(3, formula2ConstantsCount); assertEquals(1, formula3VariablesCount); assertEquals( - 0, Trees.traverse(new And(), new AtomsCount(true, true, true)).orElseThrow()); + 0, Trees.traverse(new And(), new AtomsCountTreeVisitor(true, true, true)).orElseThrow()); } @Test @@ -73,11 +76,11 @@ public void featureDensityVisitorTest() { FeatureModel featureModel = createFeatureModel(); Iterator constraintsIterator = featureModel.getConstraints().iterator(); - Set formula1Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensity()) + Set formula1Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensityTreeVisitor()) .orElseThrow(); - Set formula2Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensity()) + Set formula2Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensityTreeVisitor()) .orElseThrow(); - Set formula3Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensity()) + Set formula3Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensityTreeVisitor()) .orElseThrow(); assertEquals(5, formula1Features.size()); @@ -100,7 +103,7 @@ public void featureDensityVisitorTest() { assertTrue(formula3Features.contains("a")); assertFalse(formula3Features.contains("b")); assertEquals( - 0, Trees.traverse(new And(), new FeatureDensity()).orElseThrow().size()); + 0, Trees.traverse(new And(), new FeatureDensityTreeVisitor()).orElseThrow().size()); } @Test @@ -109,13 +112,13 @@ public void operatorDensityVisitorTest() { Iterator constraintsIterator = featureModel.getConstraints().iterator(); HashMap formula1Count = Trees.traverse( - constraintsIterator.next().getFormula(), new OperatorDistribution()) + constraintsIterator.next().getFormula(), new OperatorDistributionTreeVisitor()) .orElseThrow(); HashMap formula2Count = Trees.traverse( - constraintsIterator.next().getFormula(), new OperatorDistribution()) + constraintsIterator.next().getFormula(), new OperatorDistributionTreeVisitor()) .orElseThrow(); HashMap formula3Count = Trees.traverse( - constraintsIterator.next().getFormula(), new OperatorDistribution()) + constraintsIterator.next().getFormula(), new OperatorDistributionTreeVisitor()) .orElseThrow(); assertEquals(2, formula1Count.get("And")); @@ -133,7 +136,7 @@ public void operatorDensityVisitorTest() { assertEquals(0, formula3Count.size()); assertEquals( 0, - Trees.traverse(null, new OperatorDistribution()).orElseThrow().size()); + Trees.traverse(null, new OperatorDistributionTreeVisitor()).orElseThrow().size()); } public FeatureModel createFeatureModel() { diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index 8e22d496..edd2eb8a 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -28,6 +28,11 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IFeatureModel; import de.featjar.feature.model.TestDataProvider; +import de.featjar.feature.model.analysis.computation.ComputeDistributionFeatureSelections; +import de.featjar.feature.model.analysis.computation.ComputeFeatureCounter; +import de.featjar.feature.model.analysis.computation.ComputeNumberConfigurations; +import de.featjar.feature.model.analysis.computation.ComputeNumberVariables; +import de.featjar.feature.model.analysis.computation.ComputeUniformity; import de.featjar.feature.model.transformer.ComputeFormula; import de.featjar.formula.VariableMap; import de.featjar.formula.assignment.BooleanAssignment; diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index af55f81d..6dd669c5 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -29,6 +29,12 @@ import de.featjar.feature.model.IFeature; import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.TestDataProvider; +import de.featjar.feature.model.analysis.computation.ComputeFeatureAverageNumberOfChildren; +import de.featjar.feature.model.analysis.computation.ComputeFeatureFeaturesCounter; +import de.featjar.feature.model.analysis.computation.ComputeFeatureGroupDistribution; +import de.featjar.feature.model.analysis.computation.ComputeFeatureTopFeatures; +import de.featjar.feature.model.analysis.computation.ComputeFeatureTreeDepth; + import java.util.HashMap; import org.junit.jupiter.api.Test; diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index 90cd381f..a896be91 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -29,7 +29,7 @@ import de.featjar.base.computation.IComputation; import de.featjar.base.tree.Trees; import de.featjar.feature.model.FeatureModel; -import de.featjar.feature.model.analysis.AtomsCount; +import de.featjar.feature.model.analysis.visitor.AtomsCountTreeVisitor; import de.featjar.formula.structure.IFormula; import de.featjar.formula.structure.connective.And; import de.featjar.formula.structure.connective.Implies; @@ -51,7 +51,7 @@ public void atomsTest() { IComputation compuational = Computations.of(featureModel).map(ComputeAtomsCount::new); Trees.traverse( - featureModel.getConstraints().iterator().next().getFormula(), new AtomsCount(false, false, false)); + featureModel.getConstraints().iterator().next().getFormula(), new AtomsCountTreeVisitor(false, false, false)); assertEquals(23, compuational.compute()); assertEquals( 3, From afbcba4484bd304bbb404d4b281da003409b188d Mon Sep 17 00:00:00 2001 From: Almekkawi Date: Fri, 17 Oct 2025 16:30:37 +0200 Subject: [PATCH 245/257] fix: fix for gradle build. --- settings.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/settings.gradle b/settings.gradle index 6d8ebb49..4601bb74 100644 --- a/settings.gradle +++ b/settings.gradle @@ -7,3 +7,4 @@ plugins { } includeBuild '../formula' +includeBuild '../formula-analysis-javasmt' From b8dcda382fd2a1bc5d1323e3cc7c9c2955a8128f Mon Sep 17 00:00:00 2001 From: Benjamin von Holt Date: Fri, 17 Oct 2025 16:52:57 +0200 Subject: [PATCH 246/257] chore: ran spotless apply --- .../AnalysisTreeKeywordTreeVisitor.java | 2 -- .../AVisualizeFeatureModelStats.java | 9 ++--- .../feature/model/cli/PrintStatistics.java | 4 +-- .../model/computation/ComputeAtomsCount.java | 1 - .../computation/ComputeAverageConstraint.java | 1 - .../computation/ComputeFeatureDensity.java | 1 - .../ComputeOperatorDistribution.java | 1 - .../ConstraintPropertiesVisitorTest.java | 34 ++++++++++++------- .../model/analysis/SamplePropertiesTest.java | 6 ++-- .../analysis/SimpleTreePropertiesTest.java | 1 - .../cli/FeatureModelFormatConversionTest.java | 6 ++-- .../model/cli/PrintSampleStatisticsTest.java | 16 +++++---- .../model/cli/PrintStatisticsTest.java | 4 ++- .../computation/ConstraintPropertiesTest.java | 3 +- 14 files changed, 49 insertions(+), 40 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordTreeVisitor.java index c34357d0..f020d86e 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordTreeVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeKeywordTreeVisitor.java @@ -23,9 +23,7 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.feature.model.analysis.AnalysisTree; - import java.util.*; -import java.util.stream.Collectors; /** * Searches a given {@link AnalysisTree} for nodes containing a {@link #keyword}, and returns these nodes. diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index a5e3a844..e46f0e6e 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -149,8 +149,9 @@ private boolean chartsAreEmptyPDF() { * also alphabetically sorted.} */ public LinkedHashMap> extractAnalysisTree() { - HashMap relevantAnalysisSubTrees = - Trees.traverse(analysisTree, new AnalysisTreeKeywordTreeVisitor(getAnalysisTreeDataName())).get(); + HashMap relevantAnalysisSubTrees = Trees.traverse( + analysisTree, new AnalysisTreeKeywordTreeVisitor(getAnalysisTreeDataName())) + .get(); // preparing return value LinkedHashMap> analysisTreeData = new LinkedHashMap<>(); @@ -160,12 +161,12 @@ public LinkedHashMap> extractAnalysisTree( Object value = relevantAnalysisSubTrees.get(key); assert value != null : "Could not retrieve data called \"" + key + "\" from AnalysisTree."; - analysisTreeData.put(key , new LinkedHashMap<>()); + analysisTreeData.put(key, new LinkedHashMap<>()); if (value instanceof List) { @SuppressWarnings("unchecked") List> childAnalysisTrees = (List>) value; - for (AnalysisTree childTree: childAnalysisTrees) { + for (AnalysisTree childTree : childAnalysisTrees) { analysisTreeData.get(key).put(childTree.getName(), childTree.getValue()); } } else if (value instanceof Number) { diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 86edd026..80e9879e 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -222,10 +222,10 @@ private int writeToVisual( if (!optionParser.getResult(VISUALIZATION_CONTENT).isPresent()) { - ArrayList> combinedCharts = new ArrayList>(); + ArrayList> combinedCharts = new ArrayList<>(); combinedCharts.addAll(groupViz.getCharts()); combinedCharts.addAll(opViz.getCharts()); - groupViz.exportAllChartsToPDF(combinedCharts, outputPath.toString()); + AVisualizeFeatureModelStats.exportAllChartsToPDF(combinedCharts, outputPath.toString()); } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP) { groupViz.exportAllChartsToPDF(outputPath.toString()); diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java index e8536060..4d762bf8 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAtomsCount.java @@ -30,7 +30,6 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; import de.featjar.feature.model.analysis.visitor.AtomsCountTreeVisitor; - import java.util.Collection; import java.util.Iterator; import java.util.List; diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java index 2d45a980..39f4871f 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeAverageConstraint.java @@ -30,7 +30,6 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; import de.featjar.feature.model.analysis.visitor.AtomsCountTreeVisitor; - import java.util.Collection; import java.util.Iterator; import java.util.List; diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java index 537f089a..13c19eb3 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeFeatureDensity.java @@ -29,7 +29,6 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; import de.featjar.feature.model.analysis.visitor.FeatureDensityTreeVisitor; - import java.util.Collection; import java.util.Collections; import java.util.HashSet; diff --git a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java index d3f509d3..8c98b50b 100644 --- a/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java +++ b/src/main/java/de/featjar/feature/model/computation/ComputeOperatorDistribution.java @@ -29,7 +29,6 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.IConstraint; import de.featjar.feature.model.analysis.visitor.OperatorDistributionTreeVisitor; - import java.util.Collection; import java.util.HashMap; import java.util.Iterator; diff --git a/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java index fd5f3e57..59ee0988 100644 --- a/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/ConstraintPropertiesVisitorTest.java @@ -58,17 +58,19 @@ public void atomsVisitorTest() { IFormula formula1 = constraintsIterator.next().getFormula(); IFormula formula2 = constraintsIterator.next().getFormula(); IFormula formula3 = constraintsIterator.next().getFormula(); - int formula1BooleansCount = - Trees.traverse(formula1, new AtomsCountTreeVisitor(false, false, true)).orElseThrow(); - int formula2ConstantsCount = - Trees.traverse(formula3, new AtomsCountTreeVisitor(false, true, false)).orElseThrow(); - int formula3VariablesCount = - Trees.traverse(formula3, new AtomsCountTreeVisitor(true, false, false)).orElseThrow(); + int formula1BooleansCount = Trees.traverse(formula1, new AtomsCountTreeVisitor(false, false, true)) + .orElseThrow(); + int formula2ConstantsCount = Trees.traverse(formula3, new AtomsCountTreeVisitor(false, true, false)) + .orElseThrow(); + int formula3VariablesCount = Trees.traverse(formula3, new AtomsCountTreeVisitor(true, false, false)) + .orElseThrow(); assertEquals(1, formula1BooleansCount); assertEquals(3, formula2ConstantsCount); assertEquals(1, formula3VariablesCount); assertEquals( - 0, Trees.traverse(new And(), new AtomsCountTreeVisitor(true, true, true)).orElseThrow()); + 0, + Trees.traverse(new And(), new AtomsCountTreeVisitor(true, true, true)) + .orElseThrow()); } @Test @@ -76,11 +78,14 @@ public void featureDensityVisitorTest() { FeatureModel featureModel = createFeatureModel(); Iterator constraintsIterator = featureModel.getConstraints().iterator(); - Set formula1Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensityTreeVisitor()) + Set formula1Features = Trees.traverse( + constraintsIterator.next().getFormula(), new FeatureDensityTreeVisitor()) .orElseThrow(); - Set formula2Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensityTreeVisitor()) + Set formula2Features = Trees.traverse( + constraintsIterator.next().getFormula(), new FeatureDensityTreeVisitor()) .orElseThrow(); - Set formula3Features = Trees.traverse(constraintsIterator.next().getFormula(), new FeatureDensityTreeVisitor()) + Set formula3Features = Trees.traverse( + constraintsIterator.next().getFormula(), new FeatureDensityTreeVisitor()) .orElseThrow(); assertEquals(5, formula1Features.size()); @@ -103,7 +108,10 @@ public void featureDensityVisitorTest() { assertTrue(formula3Features.contains("a")); assertFalse(formula3Features.contains("b")); assertEquals( - 0, Trees.traverse(new And(), new FeatureDensityTreeVisitor()).orElseThrow().size()); + 0, + Trees.traverse(new And(), new FeatureDensityTreeVisitor()) + .orElseThrow() + .size()); } @Test @@ -136,7 +144,9 @@ public void operatorDensityVisitorTest() { assertEquals(0, formula3Count.size()); assertEquals( 0, - Trees.traverse(null, new OperatorDistributionTreeVisitor()).orElseThrow().size()); + Trees.traverse(null, new OperatorDistributionTreeVisitor()) + .orElseThrow() + .size()); } public FeatureModel createFeatureModel() { diff --git a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java index edd2eb8a..86713cf0 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SamplePropertiesTest.java @@ -186,9 +186,9 @@ public void computeNumberVariablesTest() { @Test public void computeUniformity() { - if (! FeatJAR.isInitialized()) { - FeatJAR.initialize(); - } + if (!FeatJAR.isInitialized()) { + FeatJAR.initialize(); + } FeatureModel testFM = TestDataProvider.createMediumFeatureModel(); IComputation> computation = Computations.of((IFeatureModel) testFM) .map(ComputeUniformity::new) diff --git a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java index 6dd669c5..ebb932b9 100644 --- a/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/analysis/SimpleTreePropertiesTest.java @@ -34,7 +34,6 @@ import de.featjar.feature.model.analysis.computation.ComputeFeatureGroupDistribution; import de.featjar.feature.model.analysis.computation.ComputeFeatureTopFeatures; import de.featjar.feature.model.analysis.computation.ComputeFeatureTreeDepth; - import java.util.HashMap; import org.junit.jupiter.api.Test; diff --git a/src/test/java/de/featjar/feature/model/cli/FeatureModelFormatConversionTest.java b/src/test/java/de/featjar/feature/model/cli/FeatureModelFormatConversionTest.java index 083a5c87..adeb8269 100644 --- a/src/test/java/de/featjar/feature/model/cli/FeatureModelFormatConversionTest.java +++ b/src/test/java/de/featjar/feature/model/cli/FeatureModelFormatConversionTest.java @@ -169,9 +169,9 @@ void infoLossMapTestTriggers() throws IOException { */ @Test void testWriteAndOverwrite() throws IOException { - if (! FeatJAR.isInitialized()) { - FeatJAR.initialize(); - } + if (!FeatJAR.isInitialized()) { + FeatJAR.initialize(); + } Path outputPath = Paths.get("model_testWriteAndOverwrite.xml"); FeatureModel model = generateModel(); diff --git a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java index e181cc68..7e96102b 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintSampleStatisticsTest.java @@ -83,9 +83,9 @@ void outputWithoutFileExtension() { @Test void printComparison() { - if (! FeatJAR.isInitialized()) { - FeatJAR.initialize(); - } + if (!FeatJAR.isInitialized()) { + FeatJAR.initialize(); + } Result loadedConfig = IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); Result loadedFM = IO.load(Paths.get(fmPath), FeatureModelFormats.getInstance()); @@ -111,9 +111,9 @@ void printComparison() { @Test void prettyStringBuilder() { - if (! FeatJAR.isInitialized()) { - FeatJAR.initialize(); - } + if (!FeatJAR.isInitialized()) { + FeatJAR.initialize(); + } Result loadedConfig = IO.load(Paths.get(configPath), BooleanAssignmentListFormats.getInstance()); Result loadedFM = IO.load(Paths.get(fmPath), FeatureModelFormats.getInstance()); @@ -188,7 +188,9 @@ void prettyStringBuilder() { + ""; LinkedHashMap map = printSampleStats.collectStats(booleanAssignmentList, model); - assertEquals(comparison.replaceAll("[^a-zA-Z1-9:]", ""), printSampleStats.buildStringPrettyStats(map).toString().replaceAll("[^a-zA-Z1-9:]", "")); + assertEquals( + comparison.replaceAll("[^a-zA-Z1-9:]", ""), + printSampleStats.buildStringPrettyStats(map).toString().replaceAll("[^a-zA-Z1-9:]", "")); FeatJAR.deinitialize(); } } diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 386f1ace..ec0b356a 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -192,7 +192,9 @@ void prettyStringBuilder() throws IOException { + "[Tree 1] Average Number of Children : \n" + ""; - assertEquals(comparison.replaceAll("[^a-zA-Z1-9:]", ""), printStats.buildStringPrettyStats(testData).toString().replaceAll("[^a-zA-Z1-9:]", "")); + assertEquals( + comparison.replaceAll("[^a-zA-Z1-9:]", ""), + printStats.buildStringPrettyStats(testData).toString().replaceAll("[^a-zA-Z1-9:]", "")); } // TODO implement this test once the jsonHashMapToTree() function works in AnalysisTreeTransformer /** diff --git a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java index a896be91..3f890770 100644 --- a/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java +++ b/src/test/java/de/featjar/feature/model/computation/ConstraintPropertiesTest.java @@ -51,7 +51,8 @@ public void atomsTest() { IComputation compuational = Computations.of(featureModel).map(ComputeAtomsCount::new); Trees.traverse( - featureModel.getConstraints().iterator().next().getFormula(), new AtomsCountTreeVisitor(false, false, false)); + featureModel.getConstraints().iterator().next().getFormula(), + new AtomsCountTreeVisitor(false, false, false)); assertEquals(23, compuational.compute()); assertEquals( 3, From 3b242ccabfafc4b1db48dca902bd78a6889cc320 Mon Sep 17 00:00:00 2001 From: Valentin Date: Fri, 24 Oct 2025 15:55:10 +0200 Subject: [PATCH 247/257] added funktion for building boxplott and new VisualizeClass with test values --- .../AVisualizeFeatureModelStats.java | 28 ++++++++++ .../VisualizeAverageNumberOfChildren.java | 54 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index e46f0e6e..6c70ae81 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -215,6 +215,34 @@ analysisTree, new AnalysisTreeKeywordTreeVisitor(getAnalysisTreeDataName())) return charts; } + /** + * Premade builder for box charts that you can use when implementing {@link #buildCharts()}. + * Not ready yet + * @return list containing one chart per tree in the feature model + */ + protected ArrayList> buildBoxCharts() { + ArrayList> charts = new ArrayList<>(); + // TODO in averagenumberofchildren only one double is provided instead of an double array, so I used testdata to + // test die BoxPlot + // TODO for example not the average number of children, but the number of children for every node as a double or + // int array is needed to plot a boxplot for the average number of children + double[] testdata = {0.9, 1.5, 2.22}; + + for (String treeKey : this.analysisTreeData.keySet()) { + // Momentan bauen wir pro Tree einen Chart mit einer Box mit mehreren Werten + // Wäre es für Boxplots nicht sinnvoller einen Chart für alle Trees zu machen, + // mit je eine Box pro Tree? + BoxChart chart = + new BoxChartBuilder().width(getWidth()).height(getHeight()).build(); + chart.setTitle(treeKey); + defaultStyler(chart); + HashMap treeData = analysisTreeData.get(treeKey); + treeData.forEach((key, value) -> chart.addSeries(key, (double[]) testdata)); + charts.add(chart); + } + return charts; + } + /** * Styles charts with the default settings, for consistency. */ diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java new file mode 100644 index 00000000..818c5c23 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java @@ -0,0 +1,54 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis.visualization; + +import de.featjar.feature.model.analysis.AnalysisTree; +import java.util.ArrayList; +import org.knowm.xchart.internal.chartpart.Chart; + +/** + * Visualizes and exports the feature model statistic "Average Number of Children". + * Data is read as an {@link AnalysisTree}. Each child specifies the information to be read from the tree via + * {@link #getAnalysisTreeDataName()}, as well as how to build a chart from it via the {@link #buildCharts()} method. + * + * @author Benjamin von Holt + * @author Valentin Laubsch + */ +public class VisualizeAverageNumberOfChildren extends AVisualizeFeatureModelStats { + /** + * Visualizes and exports the feature model statistic "Average Number of Children". + * + * @param analysisTree {@link AnalysisTree} over the entire feature model. + */ + public VisualizeAverageNumberOfChildren(AnalysisTree analysisTree) { + super(analysisTree); + } + + @Override + protected String getAnalysisTreeDataName() { + return "Average Number of Children"; + } + + @Override + protected ArrayList> buildCharts() { + return buildBoxCharts(); + } +} \ No newline at end of file From b357658e7497f96c6550e1b0fcb5e4d3f5de7e18 Mon Sep 17 00:00:00 2001 From: Valentin Date: Fri, 24 Oct 2025 17:12:00 +0200 Subject: [PATCH 248/257] modified TreeVisitor to return int array instead of result direktly, modified computation class to support legacy double return to not break old code that relies on double as a value --- ...ComputeFeatureAverageNumberOfChildren.java | 8 +++- .../TreeAvgChildrenCounterTreeVisitor.java | 38 +++++++++++++------ 2 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java index 29a94e46..64639182 100644 --- a/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java @@ -45,6 +45,12 @@ public ComputeFeatureAverageNumberOfChildren(IComputation featureT @Override public Result compute(List dependencyList, Progress progress) { IFeatureTree tree = FEATURE_TREE.get(dependencyList); - return Trees.traverse(tree, new TreeAvgChildrenCounterTreeVisitor()); + Result r = Trees.traverse(tree, new TreeAvgChildrenCounterTreeVisitor()); + if (r.isEmpty()) return Result.of(0.0); + int[] arr = r.get(); + if (arr.length == 0) return Result.of(0.0); + long sum = 0; + for (int c : arr) sum += c; + return Result.of((double) sum / arr.length); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java index 980ed63f..11fd782d 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java @@ -23,6 +23,8 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; + +import java.util.ArrayList; import java.util.List; /** @@ -32,30 +34,42 @@ * @author Valentin Laubsch * @author Benjamin von Holt */ -public class TreeAvgChildrenCounterTreeVisitor implements ITreeVisitor, Double> { - private int nodeCount = 0; - private int childCount = 0; +public class TreeAvgChildrenCounterTreeVisitor implements ITreeVisitor, int[]> { + private ArrayList childrenPerNode = new ArrayList<>(); @Override public TraversalAction firstVisit(List> path) { final ITree node = ITreeVisitor.getCurrentNode(path); - nodeCount++; - childCount += node.getChildrenCount(); + childrenPerNode.add(node.getChildrenCount()); return TraversalAction.CONTINUE; } @Override public void reset() { - nodeCount = 0; - childCount = 0; + childrenPerNode.clear(); + } + + /** + * The old Visitor calculated the average number, instead of returning the Array + * In case the old funktionality is needed it is implemented as getAverage() now. + * + * @return Average Number Of Children per Node as double for whole tree + * @author Valentin Laubsch + */ + public Result getAverage() { + if (childrenPerNode.isEmpty()) return Result.of(0.0); + long sum = 0; + for (int c : childrenPerNode) sum += c; + double avg = (double) sum / childrenPerNode.size(); + return Result.of(avg); } @Override - public Result getResult() { - double result = 0; - if (nodeCount > 0) { - result = (double) childCount / nodeCount; + public Result getResult() { + int[] resultArray = new int[childrenPerNode.size()]; + for (int i = 0; i < childrenPerNode.size(); i++) { + resultArray[i] = childrenPerNode.get(i); } - return Result.of(result); + return Result.of(resultArray); } } From 40e4e52bcc1bb3f8695ff64cc583d00074212c30 Mon Sep 17 00:00:00 2001 From: Valentin Date: Fri, 24 Oct 2025 18:11:29 +0200 Subject: [PATCH 249/257] Computation for AverageNumberOfChildren supports now both the double-for-tree and the int-array-result --- ...ComputeFeatureAverageNumberOfChildren.java | 2 +- ...eFeatureAverageNumberOfChildrenCounts.java | 51 +++++++++++++++++++ .../VisualizeAverageNumberOfChildren.java | 2 +- .../VisualizeFeatureModelStatsTest.java | 9 ++++ 4 files changed, 62 insertions(+), 2 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildrenCounts.java diff --git a/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java index 64639182..a3922046 100644 --- a/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildren.java @@ -31,7 +31,7 @@ import java.util.List; /** - * Calculates the number of features that have no child features. + * Calculates the Average Number Of Children per Node per Tree * * @author Benjamin von Holt */ diff --git a/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildrenCounts.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildrenCounts.java new file mode 100644 index 00000000..6861c8a1 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildrenCounts.java @@ -0,0 +1,51 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis.computation; + +import de.featjar.base.computation.AComputation; +import de.featjar.base.computation.Dependency; +import de.featjar.base.computation.IComputation; +import de.featjar.base.computation.Progress; +import de.featjar.base.data.Result; +import de.featjar.base.tree.Trees; +import de.featjar.feature.model.IFeatureTree; +import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounterTreeVisitor; + +import java.util.List; + +/** + * Calculates the Number of Children per Node as Array + * + * @author Valentin Laubsch + */ +public class ComputeFeatureAverageNumberOfChildrenCounts extends AComputation { + protected static final Dependency FEATURE_TREE = Dependency.newDependency(IFeatureTree.class); + + public ComputeFeatureAverageNumberOfChildrenCounts(IComputation featureTree) { + super(featureTree); + } + + @Override + public Result compute(List dependencyList, Progress progress) { + IFeatureTree tree = FEATURE_TREE.get(dependencyList); + return Trees.traverse(tree, new TreeAvgChildrenCounterTreeVisitor()); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java index 818c5c23..e8711916 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java @@ -44,7 +44,7 @@ public VisualizeAverageNumberOfChildren(AnalysisTree analysisTree) { @Override protected String getAnalysisTreeDataName() { - return "Average Number of Children"; + return "Average Number of Children Counts"; } @Override diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index f7b4c823..eb725b10 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -25,6 +25,7 @@ import de.featjar.feature.model.FeatureModel; import de.featjar.feature.model.TestDataProvider; import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.visualization.VisualizeAverageNumberOfChildren; import de.featjar.feature.model.analysis.visualization.VisualizeConstraintOperatorDistribution; import de.featjar.feature.model.analysis.visualization.VisualizeGroupDistribution; import java.io.File; @@ -111,6 +112,14 @@ void pdfInvalidIndex() { assertEquals(1, vizOpDis.exportChartToPDF(99, defaultExportName)); } + @Test + void boxPlot() { + VisualizeAverageNumberOfChildren vizAvChild; + vizAvChild = new VisualizeAverageNumberOfChildren(bigTree); + assertEquals(0, vizAvChild.exportChartToPDF(0, defaultExportName)); + assertTrue(Files.exists(Paths.get(defaultExportName))); + } + @Test void changeChartHeight() { VisualizeGroupDistribution vizGroup; From c1ee5f59275c1ad8fce0194462182127b6390164 Mon Sep 17 00:00:00 2001 From: Valentin Date: Fri, 24 Oct 2025 22:18:55 +0200 Subject: [PATCH 250/257] made analysistree and printStats compatible with arrays, also included utils vor arrays. TODO JSON YAML converter should support arrays --- .../feature/model/analysis/AnalysisTree.java | 24 ++--- .../model/analysis/util/AnalysisArrays.java | 95 +++++++++++++++++++ .../model/analysis/util/SeriesStats.java | 64 +++++++++++++ .../model/analysis/util/ValueUtils.java | 52 ++++++++++ .../AVisualizeFeatureModelStats.java | 22 +++-- .../feature/model/cli/PrintStatistics.java | 48 ++++++++-- .../transformer/AnalysisTreeTransformer.java | 7 +- .../VisualizeFeatureModelStatsTest.java | 4 +- 8 files changed, 283 insertions(+), 33 deletions(-) create mode 100644 src/main/java/de/featjar/feature/model/analysis/util/AnalysisArrays.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/util/SeriesStats.java create mode 100644 src/main/java/de/featjar/feature/model/analysis/util/ValueUtils.java diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index f19ebf44..0693b607 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -21,7 +21,10 @@ package de.featjar.feature.model.analysis; import de.featjar.base.tree.structure.ATree; +import de.featjar.feature.model.analysis.util.ValueUtils; + import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -77,30 +80,17 @@ public AnalysisTree cloneNode() { @Override public boolean equalsNode(AnalysisTree other) { - if (other.value == null && this.value != null) { - return false; - } - if (other.value != null && this.value == null) { - return false; - } - if (this.value == null && other.value == null) { - return this.name.equals(other.name); - } - return this.name.equals(other.name) && this.value.equals(other.value); + return Objects.equals(name, other.name) + && ValueUtils.equalsValue(value, other.value); } @Override public int hashCodeNode() { - return Objects.hash(this.getClass(), this.name, this.value); + return Objects.hash(getClass(), name, ValueUtils.hashValue(value)); } @Override public String toString() { - if (this.value == null) { - return "Name: " + this.name + " - Value: " + this.value + " - Value class: " + "No class"; - } else { - return "Name: " + this.name + " - Value: " + this.value + " - Value class: " - + this.value.getClass().getName(); - } + return "Name: " + name + " - Value: " + ValueUtils.toStringValue(value); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/util/AnalysisArrays.java b/src/main/java/de/featjar/feature/model/analysis/util/AnalysisArrays.java new file mode 100644 index 00000000..303dc2a2 --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/util/AnalysisArrays.java @@ -0,0 +1,95 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis.util; + +import de.featjar.feature.model.analysis.AnalysisTree; + +import java.util.Arrays; +import java.util.List; + +/** + * Utility for Series to integrate arrays in {@link AnalysisTree} + * + * + * @author Valentin Laubsch + */ +public final class AnalysisArrays { + private AnalysisArrays() {} + + /** Returns true if v is a numeric series: primitive number array or List (empty list counts as series). */ + public static boolean isSeries(Object v) { + if (v == null) return false; + if (v instanceof double[] || v instanceof int[] || v instanceof long[] || v instanceof float[]) { + return true; + } + if (v instanceof List) { + List l = (List) v; + if (l.isEmpty()) return true; // treat empty as a valid (empty) series + for (Object e : l) { + if (!(e instanceof Number)) return false; + } + return true; + } + return false; + } + + /** Converts any supported series (double[]/int[]/long[]/float[]/List) to a double[]. Unknown → empty array. */ + public static double[] toDoubleArray(Object v) { + if (v instanceof double[]) return (double[]) v; // no copy on purpose + if (v instanceof int[]) { + int[] a = (int[]) v; + double[] d = new double[a.length]; + for (int i = 0; i < a.length; i++) d[i] = a[i]; + return d; + } + if (v instanceof long[]) { + long[] a = (long[]) v; + double[] d = new double[a.length]; + for (int i = 0; i < a.length; i++) d[i] = a[i]; + return d; + } + if (v instanceof float[]) { + float[] a = (float[]) v; + double[] d = new double[a.length]; + for (int i = 0; i < a.length; i++) d[i] = a[i]; + return d; + } + if (v instanceof List) { + List l = (List) v; + double[] d = new double[l.size()]; + for (int i = 0; i < l.size(); i++) { + Object e = l.get(i); + d[i] = (e instanceof Number) ? ((Number) e).doubleValue() : Double.NaN; + } + return d; + } + return new double[0]; + } + + public static String toReadableString(Object v) { + if (v instanceof int[]) return Arrays.toString((int[]) v); + if (v instanceof double[]) return Arrays.toString((double[]) v); + if (v instanceof long[]) return Arrays.toString((long[]) v); + if (v instanceof float[]) return Arrays.toString((float[]) v); + if (v instanceof List) return toReadableString(toDoubleArray(v)); + return String.valueOf(v); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/util/SeriesStats.java b/src/main/java/de/featjar/feature/model/analysis/util/SeriesStats.java new file mode 100644 index 00000000..ecece56d --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/util/SeriesStats.java @@ -0,0 +1,64 @@ +/* + * Copyright (C) 2025 FeatJAR-Development-Team + * + * This file is part of FeatJAR-feature-model. + * + * feature-model is free software: you can redistribute it and/or modify it + * under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3.0 of the License, + * or (at your option) any later version. + * + * feature-model is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with feature-model. If not, see . + * + * See for further information. + */ +package de.featjar.feature.model.analysis.util; + +import de.featjar.feature.model.analysis.AnalysisTree; + +import java.util.Arrays; + +/** + * Utility for SeriesStats to integrate arrays in {@link AnalysisTree} + * + * + * @author Valentin Laubsch + */ +public final class SeriesStats { + private SeriesStats() {} + + public static double avg(double[] a) { + if (a.length == 0) return 0.0; + double s = 0.0; + for (double v : a) s += v; + return s / a.length; + } + + public static double median(double[] a) { + if (a.length == 0) return 0.0; + double[] c = a.clone(); + Arrays.sort(c); + int n = c.length; + return (n % 2 == 1) ? c[n / 2] : (c[n / 2 - 1] + c[n / 2]) / 2.0; + } + + public static double min(double[] a) { + if (a.length == 0) return 0.0; + double m = a[0]; + for (double v : a) if (v < m) m = v; + return m; + } + + public static double max(double[] a) { + if (a.length == 0) return 0.0; + double m = a[0]; + for (double v : a) if (v > m) m = v; + return m; + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/util/ValueUtils.java b/src/main/java/de/featjar/feature/model/analysis/util/ValueUtils.java new file mode 100644 index 00000000..9b0a9e7a --- /dev/null +++ b/src/main/java/de/featjar/feature/model/analysis/util/ValueUtils.java @@ -0,0 +1,52 @@ +package de.featjar.feature.model.analysis.util; + +import java.lang.reflect.Array; +import java.util.Arrays; +import java.util.Objects; + +public final class ValueUtils { + private ValueUtils() {} + + public static boolean equalsValue(Object a, Object b) { + if (a == b) return true; + if (a == null || b == null) return false; + + if (a.getClass().isArray() && b.getClass().isArray()) { + int lenA = Array.getLength(a); + int lenB = Array.getLength(b); + if (lenA != lenB) return false; + for (int i = 0; i < lenA; i++) { + Object elemA = Array.get(a, i); + Object elemB = Array.get(b, i); + if (!Objects.equals(elemA, elemB)) return false; + } + return true; + } + return Objects.equals(a, b); + } + + public static int hashValue(Object v) { + if (v == null) return 0; + if (v.getClass().isArray()) { + int len = Array.getLength(v); + int hash = 1; + for (int i = 0; i < len; i++) { + Object elem = Array.get(v, i); + hash = 31 * hash + Objects.hashCode(elem); + } + return hash; + } + return v.hashCode(); + } + + public static String toStringValue(Object v) { + if (v == null) return "null"; + if (v.getClass().isArray()) { + int len = Array.getLength(v); + Object[] boxed = new Object[len]; + for (int i = 0; i < len; i++) boxed[i] = Array.get(v, i); + return Arrays.toString(boxed); + } + return v.toString(); + } +} diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index 6c70ae81..a926b3bc 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -171,6 +171,8 @@ analysisTree, new AnalysisTreeKeywordTreeVisitor(getAnalysisTreeDataName())) } } else if (value instanceof Number) { analysisTreeData.get(key).put(key, value); + } else if (value instanceof double[]) { + analysisTreeData.get(key).put(key, value); } } return analysisTreeData; @@ -222,11 +224,6 @@ analysisTree, new AnalysisTreeKeywordTreeVisitor(getAnalysisTreeDataName())) */ protected ArrayList> buildBoxCharts() { ArrayList> charts = new ArrayList<>(); - // TODO in averagenumberofchildren only one double is provided instead of an double array, so I used testdata to - // test die BoxPlot - // TODO for example not the average number of children, but the number of children for every node as a double or - // int array is needed to plot a boxplot for the average number of children - double[] testdata = {0.9, 1.5, 2.22}; for (String treeKey : this.analysisTreeData.keySet()) { // Momentan bauen wir pro Tree einen Chart mit einer Box mit mehreren Werten @@ -234,10 +231,11 @@ analysisTree, new AnalysisTreeKeywordTreeVisitor(getAnalysisTreeDataName())) // mit je eine Box pro Tree? BoxChart chart = new BoxChartBuilder().width(getWidth()).height(getHeight()).build(); - chart.setTitle(treeKey); + chart.setTitle((this.chartTitle == null) ? treeKey : this.chartTitle); defaultStyler(chart); + HashMap treeData = analysisTreeData.get(treeKey); - treeData.forEach((key, value) -> chart.addSeries(key, (double[]) testdata)); + treeData.forEach((key, value) -> chart.addSeries(key, (double[]) value)); charts.add(chart); } return charts; @@ -252,6 +250,9 @@ private void defaultStyler(Chart chart) { if (chart instanceof PieChart) { defaultStylerPieChartExtension((PieChart) chart); } + if (chart instanceof BoxChart) { + defaultStylerBoxChartExtension((BoxChart) chart); + } } /** @@ -261,6 +262,13 @@ private void defaultStylerPieChartExtension(PieChart chart) { chart.getStyler().setLabelsFont(fontLabels); } + /** + * extends {@link #defaultStyler(Chart)} with BoxChart-specific calls + */ + private void defaultStylerBoxChartExtension(BoxChart chart) { + chart.getStyler().setShowWithinAreaPoint(false); + } + /** * Creates a live preview pop-up window of a chart. * @param chart the chart that will be displayed diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 80e9879e..585ff4c2 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -32,6 +32,7 @@ import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.computation.ComputeFeatureAverageNumberOfChildren; +import de.featjar.feature.model.analysis.computation.ComputeFeatureAverageNumberOfChildrenCounts; import de.featjar.feature.model.analysis.computation.ComputeFeatureFeaturesCounter; import de.featjar.feature.model.analysis.computation.ComputeFeatureGroupDistribution; import de.featjar.feature.model.analysis.computation.ComputeFeatureTopFeatures; @@ -50,6 +51,7 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; +import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -57,6 +59,9 @@ import java.util.Optional; import org.knowm.xchart.internal.chartpart.Chart; +import static de.featjar.feature.model.analysis.util.AnalysisArrays.*; +import static de.featjar.feature.model.analysis.util.SeriesStats.*; + /** * Prints statistics about a provided Feature Model. * @@ -321,6 +326,12 @@ public LinkedHashMap collectStats(FeatureModel model, AnalysesSc Computations.of(tree) .map(ComputeFeatureAverageNumberOfChildren::new) .compute()); + + data.put( + treePrefix + "Average Number of Children Counts", + toDoubleArray(Computations.of(tree) + .map(ComputeFeatureAverageNumberOfChildrenCounts::new) + .compute())); data.put( treePrefix + "Number of Top Features", Computations.of(tree) @@ -361,21 +372,46 @@ public void printStatsPretty(LinkedHashMap data) { public StringBuilder buildStringPrettyStats(LinkedHashMap data) { StringBuilder outputString = new StringBuilder(); + boolean printedConstraintHeader = false; + boolean printedTreeHeader = false; + for (Map.Entry entry : data.entrySet()) { - if (entry.getKey().equals("Number of Atoms")) { + String key = String.valueOf(entry.getKey()); + Object val = entry.getValue(); + + if (!printedConstraintHeader && key.equals("Number of Atoms")) { outputString.append(String.format("\n %-40s %n", "CONSTRAINT RELATED STATS\n")); - } else if (entry.getKey().equals("[Tree 1] Average Number of Children")) { + printedConstraintHeader = true; + + } + if (!printedTreeHeader && key.startsWith("[Tree")) { outputString.append(String.format("\n %-40s %n", "TREE RELATED STATS\n")); + printedTreeHeader = true; } - if (entry.getValue() instanceof Map) { - Map nestedMap = (Map) entry.getValue(); - outputString.append(String.format("%-40s%n", entry.getKey())); + + // For all Stats that are Series (Arrays or List + if (isSeries(val)) { + double[] series = toDoubleArray(val); + outputString.append(String.format("%-40s : %s%n", key, toReadableString(val))); + outputString.append(String.format( + "%-40s avg=%.4f median=%.4f min=%.4f max=%.4f%n", + "", + avg(series), median(series), min(series), max(series) + )); + continue; + } + + // For nested Maps like Operator or Group Distribution + if (val instanceof Map) { + Map nestedMap = (Map) val; + outputString.append(String.format("%-40s%n", key)); for (Map.Entry nestedEntry : nestedMap.entrySet()) { outputString.append(String.format( "%-40s : %s%n", " " + nestedEntry.getKey(), nestedEntry.getValue())); } } else { - outputString.append(String.format("%-40s : %s%n", entry.getKey(), entry.getValue())); + // Fallback for Numbers, Strings etc. + outputString.append(String.format("%-40s : %s%n", key, val)); } } return outputString; diff --git a/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java index fff3e3f3..565927a2 100644 --- a/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java +++ b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java @@ -31,6 +31,9 @@ import java.util.Iterator; import java.util.Map.Entry; +import static de.featjar.feature.model.analysis.util.AnalysisArrays.isSeries; +import static de.featjar.feature.model.analysis.util.AnalysisArrays.toDoubleArray; + /** * A class that handles transformations into AnalysisTree. * @@ -60,6 +63,8 @@ public static Result> hashMapToTree(HashMap hash root.addChild(new AnalysisTree<>(currentKey, (float) currentValue)); } else if (currentValue instanceof Double) { root.addChild(new AnalysisTree<>(currentKey, (double) currentValue)); + } else if (currentValue instanceof double[]) { + root.addChild(new AnalysisTree<>(currentKey, (double[]) currentValue)); } else if (currentValue instanceof HashMap) { Result> result = hashMapToTree((HashMap) currentValue, currentKey); if (result.isPresent()) { @@ -71,7 +76,7 @@ public static Result> hashMapToTree(HashMap hash } else { FeatJAR.log() .error("An innermost element of the Map data structure was not of type " - + "Float, Double, Integer, or HashMap"); + + "Float, Double, Integer, double[], or HashMap"); return Result.empty(); } } diff --git a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java index eb725b10..bc51fc71 100644 --- a/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java +++ b/src/test/java/de/featjar/feature/model/visualization/VisualizeFeatureModelStatsTest.java @@ -115,8 +115,8 @@ void pdfInvalidIndex() { @Test void boxPlot() { VisualizeAverageNumberOfChildren vizAvChild; - vizAvChild = new VisualizeAverageNumberOfChildren(bigTree); - assertEquals(0, vizAvChild.exportChartToPDF(0, defaultExportName)); + vizAvChild = new VisualizeAverageNumberOfChildren(doubleTree); + assertEquals(0, vizAvChild.exportAllChartsToPDF(defaultExportName)); assertTrue(Files.exists(Paths.get(defaultExportName))); } From f187d8b9cd47185079ce2f7de7fad844ca76af48 Mon Sep 17 00:00:00 2001 From: Valentin Date: Sun, 26 Oct 2025 15:27:13 +0100 Subject: [PATCH 251/257] modified PrintStats to include Util for Array printing, also changed one test for print statistics to match the new string --- .../feature/model/cli/PrintStatistics.java | 34 ++++++++++++++++++- .../model/cli/PrintStatisticsTest.java | 6 ++-- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 585ff4c2..a344dc9c 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -37,6 +37,7 @@ import de.featjar.feature.model.analysis.computation.ComputeFeatureGroupDistribution; import de.featjar.feature.model.analysis.computation.ComputeFeatureTopFeatures; import de.featjar.feature.model.analysis.computation.ComputeFeatureTreeDepth; +import de.featjar.feature.model.analysis.util.ValueUtils; import de.featjar.feature.model.analysis.visualization.*; import de.featjar.feature.model.computation.ComputeAtomsCount; import de.featjar.feature.model.computation.ComputeAverageConstraint; @@ -422,7 +423,38 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) * @param data: the previously computed data packaged line by line: String names the stat, Object holds the data. */ public void printStats(LinkedHashMap data) { - FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + data); + StringBuilder sb = new StringBuilder("{"); + + boolean first = true; + for (Map.Entry entry : data.entrySet()) { + if (!first) sb.append(", "); + sb.append(entry.getKey()).append("="); + sb.append(ValueUtils.toStringValue(entry.getValue())); + first = false; + } + sb.append("}"); + + FeatJAR.log().message("STATISTICS ABOUT THE FEATURE MODEL:\n" + sb); + } + + /** + * Util for Test + * @param data + * @return + */ + public static String mapToString(Map data) { + StringBuilder sb = new StringBuilder("{"); + + boolean first = true; + for (Map.Entry entry : data.entrySet()) { + if (!first) sb.append(", "); + sb.append(entry.getKey()).append("="); + sb.append(ValueUtils.toStringValue(entry.getValue())); + first = false; + } + sb.append("}"); + + return sb.toString(); } /** diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index ec0b356a..26094177 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -130,9 +130,9 @@ void outputWithoutFileExtension() throws IOException { @Test void scopeAll() throws IOException { String content = - "{Number of Atoms=0, Feature Density=0.0, Average Constraints=0.0, [Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; - String comparison = - printStats.collectStats(minimalModel, AnalysesScope.ALL).toString(); + "{Number of Atoms=0, Feature Density=0.0, Average Constraints=0.0, [Tree 1] Average Number of Children=0.0, [Tree 1] Average Number of Children Counts=[0.0], [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + LinkedHashMap data = printStats.collectStats(minimalModel, AnalysesScope.ALL); + String comparison = PrintStatistics.mapToString(data); assertEquals(content, comparison); } From 8292529c3fed576faa61b747910a79a384a98446 Mon Sep 17 00:00:00 2001 From: Valentin Date: Sun, 26 Oct 2025 15:49:42 +0100 Subject: [PATCH 252/257] corrected 2 more tests, to use the new print utility and to handle the new format --- .../feature/model/cli/PrintStatisticsTest.java | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 26094177..0cf44d1f 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -143,10 +143,9 @@ void scopeAll() throws IOException { void scopeTreeRelated() throws IOException { String content = - "{[Tree 1] Average Number of Children=0.0, [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; - String comparison = printStats - .collectStats(minimalModel, AnalysesScope.TREE_RELATED) - .toString(); + "{[Tree 1] Average Number of Children=0.0, [Tree 1] Average Number of Children Counts=[0.0], [Tree 1] Number of Top Features=0, [Tree 1] Number of Leaf Features=1, [Tree 1] Tree Depth=1, [Tree 1] Group Distribution={AlternativeGroup=0, AndGroup=1, OtherGroup=0, OrGroup=0}}"; + LinkedHashMap data = printStats.collectStats(minimalModel, AnalysesScope.TREE_RELATED); + String comparison = PrintStatistics.mapToString(data); assertEquals(content, comparison); } @@ -155,11 +154,9 @@ void scopeTreeRelated() throws IOException { */ @Test void scopeConstraintRelated() throws IOException { - String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=0.0}" + ""; - String comparison = printStats - .collectStats(minimalModel, AnalysesScope.CONSTRAINT_RELATED) - .toString(); - + String content = "{Number of Atoms=0, Feature Density=0.0, Average Constraints=0.0}"; + LinkedHashMap data = printStats.collectStats(minimalModel, AnalysesScope.CONSTRAINT_RELATED); + String comparison = PrintStatistics.mapToString(data); assertEquals(content, comparison); } @@ -189,8 +186,7 @@ void prettyStringBuilder() throws IOException { + "\n" + " TREE RELATED STATS\n" + " \n" - + "[Tree 1] Average Number of Children : \n" - + ""; + + "[Tree 1] Average Number of Children : \n"; assertEquals( comparison.replaceAll("[^a-zA-Z1-9:]", ""), From 6d6c7c451ef33e4a0384d42dd062a9567967de59 Mon Sep 17 00:00:00 2001 From: Valentin Date: Wed, 29 Oct 2025 20:33:40 +0100 Subject: [PATCH 253/257] integrated the new visualization class for avgChildren in the cli --- .../feature/model/cli/PrintStatistics.java | 30 +++++++++++++------ 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index a344dc9c..151e6f22 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -78,7 +78,8 @@ public enum AnalysesScope { public enum Visualize { GROUP, - OPERATOR + OPERATOR, + CHILDREN } private int exitStatus = 0; @@ -141,6 +142,7 @@ public int run(OptionList optionParser) { AnalysisTreeTransformer.hashMapToTree(data, "Analysis").get(); VisualizeGroupDistribution groupViz = new VisualizeGroupDistribution(tree); VisualizeConstraintOperatorDistribution opViz = new VisualizeConstraintOperatorDistribution(tree); + VisualizeAverageNumberOfChildren avgChildViz = new VisualizeAverageNumberOfChildren(tree); // if output path is specified, write statistics to file if (optionParser.getResult(OUTPUT_OPTION).isPresent()) { @@ -154,7 +156,7 @@ public int run(OptionList optionParser) { } if (output_extension.equals("pdf")) { - exitStatus = writeToVisual(optionParser, outputPath, groupViz, opViz); + exitStatus = writeToVisual(optionParser, outputPath, groupViz, opViz, avgChildViz); } else { exitStatus = writeToDoc(outputPath, data); FeatJAR.log().message("Statistics saved at: " + outputPath); @@ -162,7 +164,7 @@ public int run(OptionList optionParser) { } if (optionParser.get(SHOW)) { - exitStatus = show(optionParser, groupViz, opViz); + exitStatus = show(optionParser, groupViz, opViz, avgChildViz); } return exitStatus; } @@ -193,20 +195,25 @@ private int checkAndWarnOverwrite(OptionList optionParser, Path outputPath) { * @param optionParser: expects OptionList with command line arguments * @param groupViz: expects VisualizeGroupDistribution object for data visualization of group distribution * @param opViz: expects VisualizeConstraintOperatorDistribution object for data visualization of operator distribution + * @param avgChildViz: expects VisualizeAverageNumberOfChildren object for data visualization of averageNumberOfChildren */ private int show( OptionList optionParser, VisualizeGroupDistribution groupViz, - VisualizeConstraintOperatorDistribution opViz) { + VisualizeConstraintOperatorDistribution opViz, + VisualizeAverageNumberOfChildren avgChildViz) { if (!optionParser.getResult(VISUALIZATION_CONTENT).isPresent()) { opViz.displayAllCharts(); groupViz.displayAllCharts(); + avgChildViz.displayAllCharts(); } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP) { groupViz.displayAllCharts(); } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.OPERATOR) { opViz.displayAllCharts(); + } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.CHILDREN) { + avgChildViz.displayAllCharts(); } return 0; @@ -218,25 +225,30 @@ private int show( * @param outputPath: expects Path to output file location * @param groupViz: expects VisualizeGroupDistribution object for data visualization of group distribution * @param opViz: expects VisualizeConstraintOperatorDistribution object for data visualization of operator distribution + * @param avgChildViz: expects VisualizeAverageNumberOfChildren object for data visualization of averageNumberOfChildren * */ private int writeToVisual( OptionList optionParser, Path outputPath, VisualizeGroupDistribution groupViz, - VisualizeConstraintOperatorDistribution opViz) { + VisualizeConstraintOperatorDistribution opViz, + VisualizeAverageNumberOfChildren avgChildViz) { if (!optionParser.getResult(VISUALIZATION_CONTENT).isPresent()) { ArrayList> combinedCharts = new ArrayList<>(); combinedCharts.addAll(groupViz.getCharts()); combinedCharts.addAll(opViz.getCharts()); + combinedCharts.addAll(avgChildViz.getCharts()); AVisualizeFeatureModelStats.exportAllChartsToPDF(combinedCharts, outputPath.toString()); } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP) { groupViz.exportAllChartsToPDF(outputPath.toString()); - } else { + } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.OPERATOR) { opViz.exportAllChartsToPDF(outputPath.toString()); + } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.CHILDREN) { + avgChildViz.exportAllChartsToPDF(outputPath.toString()); } if (Files.exists(outputPath)) { FeatJAR.log().message("Statistics visual saved at: " + outputPath); @@ -390,7 +402,7 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) printedTreeHeader = true; } - // For all Stats that are Series (Arrays or List + // For all Stats that are Series if (isSeries(val)) { double[] series = toDoubleArray(val); outputString.append(String.format("%-40s : %s%n", key, toReadableString(val))); @@ -438,8 +450,8 @@ public void printStats(LinkedHashMap data) { } /** - * Util for Test - * @param data + * Util for testing the printStats Method. It returns the String instead of putting it in the output stream + * @param data the previously computed data packaged line by line: String names the stat, Object holds the data. * @return */ public static String mapToString(Map data) { From df0ef96d4057d2ce87b88cfe12b2d2977ce2e65d Mon Sep 17 00:00:00 2001 From: Valentin Date: Wed, 29 Oct 2025 22:05:23 +0100 Subject: [PATCH 254/257] added support for json and yaml export and run spotless apply --- .../feature/model/analysis/AnalysisTree.java | 5 +- ...eFeatureAverageNumberOfChildrenCounts.java | 1 - .../model/analysis/util/AnalysisArrays.java | 9 ++-- .../model/analysis/util/SeriesStats.java | 1 - .../TreeAvgChildrenCounterTreeVisitor.java | 1 - .../AVisualizeFeatureModelStats.java | 2 +- .../VisualizeAverageNumberOfChildren.java | 2 +- .../feature/model/cli/PrintStatistics.java | 14 ++--- .../transformer/AnalysisTreeTransformer.java | 53 ++++++++++++++++--- .../model/cli/PrintStatisticsTest.java | 2 + .../resources/expected_jsonOutputTest.json | 5 ++ .../cli/resources/expected_yamlOuputTest.yaml | 2 + 12 files changed, 66 insertions(+), 31 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java index 0693b607..9654cc77 100644 --- a/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java +++ b/src/main/java/de/featjar/feature/model/analysis/AnalysisTree.java @@ -22,9 +22,7 @@ import de.featjar.base.tree.structure.ATree; import de.featjar.feature.model.analysis.util.ValueUtils; - import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.Objects; @@ -80,8 +78,7 @@ public AnalysisTree cloneNode() { @Override public boolean equalsNode(AnalysisTree other) { - return Objects.equals(name, other.name) - && ValueUtils.equalsValue(value, other.value); + return Objects.equals(name, other.name) && ValueUtils.equalsValue(value, other.value); } @Override diff --git a/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildrenCounts.java b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildrenCounts.java index 6861c8a1..1b3af27a 100644 --- a/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildrenCounts.java +++ b/src/main/java/de/featjar/feature/model/analysis/computation/ComputeFeatureAverageNumberOfChildrenCounts.java @@ -28,7 +28,6 @@ import de.featjar.base.tree.Trees; import de.featjar.feature.model.IFeatureTree; import de.featjar.feature.model.analysis.visitor.TreeAvgChildrenCounterTreeVisitor; - import java.util.List; /** diff --git a/src/main/java/de/featjar/feature/model/analysis/util/AnalysisArrays.java b/src/main/java/de/featjar/feature/model/analysis/util/AnalysisArrays.java index 303dc2a2..5fdd960b 100644 --- a/src/main/java/de/featjar/feature/model/analysis/util/AnalysisArrays.java +++ b/src/main/java/de/featjar/feature/model/analysis/util/AnalysisArrays.java @@ -21,7 +21,6 @@ package de.featjar.feature.model.analysis.util; import de.featjar.feature.model.analysis.AnalysisTree; - import java.util.Arrays; import java.util.List; @@ -85,11 +84,11 @@ public static double[] toDoubleArray(Object v) { } public static String toReadableString(Object v) { - if (v instanceof int[]) return Arrays.toString((int[]) v); + if (v instanceof int[]) return Arrays.toString((int[]) v); if (v instanceof double[]) return Arrays.toString((double[]) v); - if (v instanceof long[]) return Arrays.toString((long[]) v); - if (v instanceof float[]) return Arrays.toString((float[]) v); - if (v instanceof List) return toReadableString(toDoubleArray(v)); + if (v instanceof long[]) return Arrays.toString((long[]) v); + if (v instanceof float[]) return Arrays.toString((float[]) v); + if (v instanceof List) return toReadableString(toDoubleArray(v)); return String.valueOf(v); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/util/SeriesStats.java b/src/main/java/de/featjar/feature/model/analysis/util/SeriesStats.java index ecece56d..3b321461 100644 --- a/src/main/java/de/featjar/feature/model/analysis/util/SeriesStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/util/SeriesStats.java @@ -21,7 +21,6 @@ package de.featjar.feature.model.analysis.util; import de.featjar.feature.model.analysis.AnalysisTree; - import java.util.Arrays; /** diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java index 11fd782d..b3180adb 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/TreeAvgChildrenCounterTreeVisitor.java @@ -23,7 +23,6 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.structure.ITree; import de.featjar.base.tree.visitor.ITreeVisitor; - import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java index a926b3bc..7b30d6f8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/AVisualizeFeatureModelStats.java @@ -171,7 +171,7 @@ analysisTree, new AnalysisTreeKeywordTreeVisitor(getAnalysisTreeDataName())) } } else if (value instanceof Number) { analysisTreeData.get(key).put(key, value); - } else if (value instanceof double[]) { + } else if (value instanceof double[]) { analysisTreeData.get(key).put(key, value); } } diff --git a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java index e8711916..551c8558 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java +++ b/src/main/java/de/featjar/feature/model/analysis/visualization/VisualizeAverageNumberOfChildren.java @@ -51,4 +51,4 @@ protected String getAnalysisTreeDataName() { protected ArrayList> buildCharts() { return buildBoxCharts(); } -} \ No newline at end of file +} diff --git a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java index 151e6f22..4ea835dc 100644 --- a/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java +++ b/src/main/java/de/featjar/feature/model/cli/PrintStatistics.java @@ -20,6 +20,9 @@ */ package de.featjar.feature.model.cli; +import static de.featjar.feature.model.analysis.util.AnalysisArrays.*; +import static de.featjar.feature.model.analysis.util.SeriesStats.*; + import de.featjar.base.FeatJAR; import de.featjar.base.cli.ACommand; import de.featjar.base.cli.Option; @@ -52,7 +55,6 @@ import java.nio.file.Files; import java.nio.file.Path; import java.util.ArrayList; -import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; @@ -60,9 +62,6 @@ import java.util.Optional; import org.knowm.xchart.internal.chartpart.Chart; -import static de.featjar.feature.model.analysis.util.AnalysisArrays.*; -import static de.featjar.feature.model.analysis.util.SeriesStats.*; - /** * Prints statistics about a provided Feature Model. * @@ -245,7 +244,7 @@ private int writeToVisual( } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.GROUP) { groupViz.exportAllChartsToPDF(outputPath.toString()); - } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.OPERATOR) { + } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.OPERATOR) { opViz.exportAllChartsToPDF(outputPath.toString()); } else if (optionParser.get(VISUALIZATION_CONTENT) == Visualize.CHILDREN) { avgChildViz.exportAllChartsToPDF(outputPath.toString()); @@ -395,7 +394,6 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) if (!printedConstraintHeader && key.equals("Number of Atoms")) { outputString.append(String.format("\n %-40s %n", "CONSTRAINT RELATED STATS\n")); printedConstraintHeader = true; - } if (!printedTreeHeader && key.startsWith("[Tree")) { outputString.append(String.format("\n %-40s %n", "TREE RELATED STATS\n")); @@ -408,9 +406,7 @@ public StringBuilder buildStringPrettyStats(LinkedHashMap data) outputString.append(String.format("%-40s : %s%n", key, toReadableString(val))); outputString.append(String.format( "%-40s avg=%.4f median=%.4f min=%.4f max=%.4f%n", - "", - avg(series), median(series), min(series), max(series) - )); + "", avg(series), median(series), min(series), max(series))); continue; } diff --git a/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java index 565927a2..ed82474f 100644 --- a/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java +++ b/src/main/java/de/featjar/feature/model/io/transformer/AnalysisTreeTransformer.java @@ -20,6 +20,9 @@ */ package de.featjar.feature.model.io.transformer; +import static de.featjar.feature.model.analysis.util.AnalysisArrays.isSeries; +import static de.featjar.feature.model.analysis.util.AnalysisArrays.toDoubleArray; + import de.featjar.base.FeatJAR; import de.featjar.base.data.Result; import de.featjar.feature.model.analysis.AnalysisTree; @@ -31,9 +34,6 @@ import java.util.Iterator; import java.util.Map.Entry; -import static de.featjar.feature.model.analysis.util.AnalysisArrays.isSeries; -import static de.featjar.feature.model.analysis.util.AnalysisArrays.toDoubleArray; - /** * A class that handles transformations into AnalysisTree. * @@ -126,10 +126,14 @@ public static Result> jsonHashMapToTree(HashMap + "was not from the type String"); return Result.empty(); } - if (!(currentElement.get(2) instanceof BigDecimal || currentElement.get(2) instanceof Integer)) { - FeatJAR.log() - .error("The third element of an innermost element of the Map/YAML data structure " - + "was not from the type String"); + if (!(currentElement.get(2) instanceof BigDecimal + || currentElement.get(2) instanceof Integer + || currentElement.get(2) instanceof Double + || (((currentElement.get(1).equals("double[]")) + || (currentElement.get(1).equals("[D"))) + && (currentElement.get(2) instanceof ArrayList + || currentElement.get(2) instanceof double[])))) { + FeatJAR.log().error("The third element ... was not from the type String"); return Result.empty(); } @@ -155,6 +159,21 @@ public static Result> jsonHashMapToTree(HashMap } else { return Result.empty(); } + } else if (currentElement.get(1).equals("double[]") + || currentElement.get(1).equals("[D")) { + if (currentElement.get(2) instanceof ArrayList) { + ArrayList list = (ArrayList) currentElement.get(2); + if (isSeries(list)) { + root.addChild(new AnalysisTree<>(currentKey, toDoubleArray(list))); + } else { + FeatJAR.log().error("Expected numeric list for key " + currentKey); + return Result.empty(); + } + } else if (currentElement.get(2) instanceof double[]) { + root.addChild(new AnalysisTree<>(currentKey, (double[]) currentElement.get(2))); + } else { + return Result.empty(); + } } } } @@ -220,7 +239,11 @@ public static Result> yamlHashMapToTree(HashMap + "was not from the type String"); return Result.empty(); } - if (!(currentElement.get(2) instanceof Double || currentElement.get(2) instanceof Integer)) { + if (!(currentElement.get(2) instanceof Double + || currentElement.get(2) instanceof Integer + || (((currentElement.get(1)).equals("double[]") || (currentElement.get(1)).equals("[D")) + && (currentElement.get(2) instanceof ArrayList + || currentElement.get(2) instanceof double[])))) { FeatJAR.log() .error("The third element of an innermost element of the Map/YAML data structure " + "was not from the type String"); @@ -236,6 +259,20 @@ public static Result> yamlHashMapToTree(HashMap double currentDouble = (double) currentElement.get(2); float currentDeccimal = (float) currentDouble; root.addChild(new AnalysisTree<>(currentKey, currentDeccimal)); + } else if ("double[]".equals(typeString) || "[D".equals(typeString)) { + if (currentElement.get(2) instanceof ArrayList) { + ArrayList list = (ArrayList) currentElement.get(2); + if (isSeries(list)) { + root.addChild(new AnalysisTree<>(currentKey, toDoubleArray(list))); + } else { + FeatJAR.log().error("Expected numeric list for key " + currentKey); + return Result.empty(); + } + } else if (currentElement.get(2) instanceof double[]) { + root.addChild(new AnalysisTree<>(currentKey, (double[]) currentElement.get(2))); + } else { + return Result.empty(); + } } } } diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 0cf44d1f..7ab98f34 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -34,6 +34,7 @@ import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; import java.io.IOException; import java.nio.file.Files; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; @@ -269,6 +270,7 @@ void csvOutputTest() throws IOException { + "csv;Feature Density;java.lang.Float;0.33333334\n" + "csv;Average Constraints;java.lang.Float;1.0\n" + "csv;[Tree 1] Average Number of Children;java.lang.Double;0.6666666666666666\n" + + "csv;[Tree 1] Average Number of Children Counts;[D;2.0;0.0;0.0\n" + "csv;[Tree 1] Number of Top Features;java.lang.Integer;2\n" + "csv;[Tree 1] Number of Leaf Features;java.lang.Integer;2\n" + "csv;[Tree 1] Tree Depth;java.lang.Integer;2\n" diff --git a/src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json b/src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json index a49170da..1e06f2e3 100644 --- a/src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json +++ b/src/test/java/de/featjar/feature/model/cli/resources/expected_jsonOutputTest.json @@ -14,6 +14,11 @@ "java.lang.Float", 1 ], + "[Tree 1] Average Number of Children Counts": [ + "[Tree 1] Average Number of Children Counts", + "[D", + [ 2.0, 0.0, 0.0] + ], "[Tree 1] Group Distribution": { "AlternativeGroup": [ "AlternativeGroup", diff --git a/src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml b/src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml index 68ab1333..174c4015 100644 --- a/src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml +++ b/src/test/java/de/featjar/feature/model/cli/resources/expected_yamlOuputTest.yaml @@ -2,6 +2,8 @@ yaml: Number of Atoms: [Number of Atoms, java.lang.Integer, 1] Feature Density: [Feature Density, java.lang.Float, 0.33333334] Average Constraints: [Average Constraints, java.lang.Float, 1.0] + '[Tree 1] Average Number of Children Counts': [ '[Tree 1] Average Number of Children Counts', '[D', + [ 2.0, 0.0, 0.0 ] ] '[Tree 1] Group Distribution': AlternativeGroup: [AlternativeGroup, java.lang.Integer, 0] AndGroup: [AndGroup, java.lang.Integer, 3] From 90a2bc7f50297795d3063e217da2b5d41c8f0853 Mon Sep 17 00:00:00 2001 From: Valentin Date: Wed, 29 Oct 2025 22:07:11 +0100 Subject: [PATCH 255/257] deleted unused import --- .../java/de/featjar/feature/model/cli/PrintStatisticsTest.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 7ab98f34..37a29fcb 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -34,7 +34,6 @@ import de.featjar.feature.model.io.yaml.YAMLAnalysisFormat; import java.io.IOException; import java.nio.file.Files; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.LinkedHashMap; import org.junit.jupiter.api.Test; From 0391c2dc877c96ae4d72c3276667781601ecf2de Mon Sep 17 00:00:00 2001 From: Valentin Date: Sun, 2 Nov 2025 13:58:22 +0100 Subject: [PATCH 256/257] csv now prints arrays in one cell instead of multiple collumns --- .../model/analysis/visitor/AnalysisTreeVisitorCSV.java | 9 ++++++++- .../featjar/feature/model/cli/PrintStatisticsTest.java | 2 +- 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java index 21817208..9b06a7f8 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java @@ -23,6 +23,8 @@ import de.featjar.base.data.Result; import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.feature.model.analysis.AnalysisTree; +import de.featjar.feature.model.analysis.util.AnalysisArrays; + import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -42,8 +44,13 @@ public TraversalAction firstVisit(List> path) { final AnalysisTree node = ITreeVisitor.getCurrentNode(path); if (node.getChildrenCount() == 0 && ITreeVisitor.getParentNode(path).isPresent()) { final AnalysisTree parent = ITreeVisitor.getParentNode(path).get(); + + final Object rawValue = node.getValue(); + final Object valueForCsv = AnalysisArrays.toReadableString(rawValue); + final String className = rawValue.getClass().getName(); + nodesList.add(Arrays.asList( - parent.getName(), node.getName(), node.getValue().getClass().getName(), node.getValue())); + parent.getName(), node.getName(), className, valueForCsv)); } return TraversalAction.CONTINUE; } diff --git a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java index 37a29fcb..84a9e15a 100644 --- a/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java +++ b/src/test/java/de/featjar/feature/model/cli/PrintStatisticsTest.java @@ -269,7 +269,7 @@ void csvOutputTest() throws IOException { + "csv;Feature Density;java.lang.Float;0.33333334\n" + "csv;Average Constraints;java.lang.Float;1.0\n" + "csv;[Tree 1] Average Number of Children;java.lang.Double;0.6666666666666666\n" - + "csv;[Tree 1] Average Number of Children Counts;[D;2.0;0.0;0.0\n" + + "csv;[Tree 1] Average Number of Children Counts;[D;[2.0, 0.0, 0.0]\n" + "csv;[Tree 1] Number of Top Features;java.lang.Integer;2\n" + "csv;[Tree 1] Number of Leaf Features;java.lang.Integer;2\n" + "csv;[Tree 1] Tree Depth;java.lang.Integer;2\n" From 3a82a07ccbfd1eb25cd3730dadbbee1ff259378e Mon Sep 17 00:00:00 2001 From: Valentin Date: Sun, 2 Nov 2025 13:59:51 +0100 Subject: [PATCH 257/257] run spotless apply --- .../model/analysis/visitor/AnalysisTreeVisitorCSV.java | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java index 9b06a7f8..0efbfda9 100644 --- a/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java +++ b/src/main/java/de/featjar/feature/model/analysis/visitor/AnalysisTreeVisitorCSV.java @@ -24,7 +24,6 @@ import de.featjar.base.tree.visitor.ITreeVisitor; import de.featjar.feature.model.analysis.AnalysisTree; import de.featjar.feature.model.analysis.util.AnalysisArrays; - import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -49,8 +48,7 @@ public TraversalAction firstVisit(List> path) { final Object valueForCsv = AnalysisArrays.toReadableString(rawValue); final String className = rawValue.getClass().getName(); - nodesList.add(Arrays.asList( - parent.getName(), node.getName(), className, valueForCsv)); + nodesList.add(Arrays.asList(parent.getName(), node.getName(), className, valueForCsv)); } return TraversalAction.CONTINUE; }