diff --git a/src/main/java/dss/lingvo/samples/TT2HFLTSCoordinator.java b/src/main/java/dss/lingvo/samples/TT2HFLTSCoordinator.java index 2a9e5a9..bf206b4 100644 --- a/src/main/java/dss/lingvo/samples/TT2HFLTSCoordinator.java +++ b/src/main/java/dss/lingvo/samples/TT2HFLTSCoordinator.java @@ -115,11 +115,12 @@ private void processMultiLevelAdvancedSample(File inputFile, File outputDirector // Step 1. Aggregate by abstraction level TT2HFLTSMHTWOWAMultiLevelOperator tt2HFLTSMHTWOWAMultiLevelOperator = new TT2HFLTSMHTWOWAMultiLevelOperator(); - List>> allByLevel = tt2HFLTSMHTWOWAMultiLevelOperator.aggregateByAbstractionLevel(model.getCriteria(), model.getAbstractionLevels(), all, targetScaleSize); + List>> allByLevel = tt2HFLTSMHTWOWAMultiLevelOperator.aggregateByAbstractionLevel(model.getCriteria(), model.getAbstractionLevels(), all, targetScaleSize, model.getCriteriaWeightsPerGroup()); List>> allByExpert = tt2HFLTSMHTWOWAMultiLevelOperator.transposeByAbstractionLevel(model.getAbstractionLevels().size(), model.getAlternatives().size(), model.getExperts().size(), allByLevel); - float[] a = new float[model.getExpertWeightsRule().values().size()]; + int numExperts = model.getExpertWeightsRule().values().size(); + float[] a = new float[numExperts]; float curMax = 0f; for (Map.Entry e : model.getExpertWeightsRule().entrySet()) { if (e.getKey().equals("1")) { @@ -128,7 +129,9 @@ private void processMultiLevelAdvancedSample(File inputFile, File outputDirector } } a[0] = curMax; - a[1] = 1 - curMax; + if (numExperts > 1) { + a[1] = 1 - curMax; + } List> altToLevel = tt2HFLTSMHTWOWAMultiLevelOperator.aggregateByExpert(model.getAbstractionLevels().size(), model.getAlternatives().size(), 7, allByExpert, a); List altVec = tt2HFLTSMHTWOWAMultiLevelOperator.aggregateFinalAltEst(7, altToLevel); diff --git a/src/main/java/dss/lingvo/t2hflts/multilevel/TT2HFLTSMHTWOWAMultiLevelOperator.java b/src/main/java/dss/lingvo/t2hflts/multilevel/TT2HFLTSMHTWOWAMultiLevelOperator.java index 3f99b5f..c510082 100644 --- a/src/main/java/dss/lingvo/t2hflts/multilevel/TT2HFLTSMHTWOWAMultiLevelOperator.java +++ b/src/main/java/dss/lingvo/t2hflts/multilevel/TT2HFLTSMHTWOWAMultiLevelOperator.java @@ -12,7 +12,11 @@ import java.util.stream.IntStream; public class TT2HFLTSMHTWOWAMultiLevelOperator { - public List>> aggregateByAbstractionLevel(Map> criteria, List abstractionLevels, List>> all, int targetScaleSize) { + public List>> aggregateByAbstractionLevel(Map> criteria, + List abstractionLevels, + List>> all, + int targetScaleSize, Map> criteriaWeightsPerGroup) { List orderedCriteria = TTUtils.getOrderedCriteriaList(criteria, abstractionLevels); TT2HFLTSMHTWAOperator tt2HFLTSMHTWAOperator = new TT2HFLTSMHTWAOperator(); List>> expEstimates = new ArrayList<>(); @@ -36,8 +40,20 @@ public List>> aggregateByAbstractionLevel(Map jsonWeights = criteriaWeightsPerGroup.get(ttAbstractionLevelModel.getAbstractionLevelID()); + weights = new float[jsonWeights.size()]; + int i = 0; + for (Float f : jsonWeights) { + weights[i++] = (f != null ? f : Float.NaN); + } + } TT2HFLTS aggRes = tt2HFLTSMHTWAOperator.calculate(tmp, weights, targetScaleSize); levelEstimates.add(aggRes); } diff --git a/src/main/java/dss/lingvo/utils/TTUtils.java b/src/main/java/dss/lingvo/utils/TTUtils.java index 7fb3ec2..ee64dae 100644 --- a/src/main/java/dss/lingvo/utils/TTUtils.java +++ b/src/main/java/dss/lingvo/utils/TTUtils.java @@ -1,6 +1,5 @@ package dss.lingvo.utils; -import dss.lingvo.hflts.TTHFLTS; import dss.lingvo.t2.TTNormalizedTranslator; import dss.lingvo.t2.TTTuple; import dss.lingvo.t2hflts.TT2HFLTS; @@ -250,7 +249,10 @@ private static float calculatePredessorsWeightsSum(float firstWeight, int curren public static List>> getAllEstimationsFromMultiLevelJSONModel(TTJSONMultiLevelInputModel ttjsonModel, int targetScaleSize) { Map> estimations = ttjsonModel.getEstimations(); - Map averages = getAverageForEachNumericCriterion(estimations); + Map averages = getAverageForEachNumericCriterion( + estimations, + ttjsonModel.getCriteria(), + ttjsonModel.getScales()); Map> criteria = ttjsonModel.getCriteria(); List levels = ttjsonModel.getAbstractionLevels(); List>> expertsEstimationsList = new ArrayList<>(); @@ -274,16 +276,34 @@ public static List>> getAllEstimationsFromMultiLev .orElse(null); TT2HFLTS res; - if (critEst.getQualitative()) { + TTCriteriaModel criterion = getCriterion(criteriaModel.getCriteriaID(), criteria); + TTScaleModel scale = getScale(critEst.getScaleID(), ttjsonModel.getScales()); + + if (isQualitativeAssessment(criterion, critEst) && + !isCrispQualitativeAssessment(scale)) { // transform it to 2tuple as usual linguistic info res = transformToTTHFLTS(ttjsonModel.getScales(), critEst.getScaleID(), critEst.getEstimation()); } else { // transform numeric to tuple and then to TTHFLTS - // however, first of all we need to normalize the values - float numericEstimation = Integer.parseInt(critEst.getEstimation().get(0)) / ((float) averages.get(critEst.getCriteriaID())); - List fSet = TTNormalizedTranslator.getInstance().getFuzzySetForNumericEstimation(numericEstimation, targetScaleSize); - float resTranslation = TTNormalizedTranslator.getInstance().getTranslationFromFuzzySet(fSet); - TTTuple resTuple = TTNormalizedTranslator.getInstance().getTTupleForNumericTranslation(resTranslation, targetScaleSize); + // however, first we need to normalize the values + Float valueToRemember; + if (isQualitativeAssessment(criterion, critEst) && isCrispQualitativeAssessment(scale)) { + // transform it to number according to the specified mapping + valueToRemember = findReplacingCrispLinguisticValue( + critEst.getEstimation().get(0), + scale); + } else { + valueToRemember = Float.parseFloat(critEst.getEstimation().get(0)); + } + + TTNormalizedTranslator translator = TTNormalizedTranslator.getInstance(); + + float numericEstimation = valueToRemember / (averages.get(critEst.getCriteriaID()).floatValue()); + + List fSet = translator.getFuzzySetForNumericEstimation(numericEstimation, targetScaleSize); + float resTranslation = translator.getTranslationFromFuzzySet(fSet); + TTTuple resTuple = translator.getTTupleForNumericTranslation(resTranslation, targetScaleSize); + List tmpL = new ArrayList<>(); tmpL.add(resTuple); res = new TT2HFLTS(tmpL); @@ -298,29 +318,93 @@ public static List>> getAllEstimationsFromMultiLev return expertsEstimationsList; } - private static Map getAverageForEachNumericCriterion(Map> estimationsMap) { - Map> averages = new TreeMap<>(); - Map sumFinal = new TreeMap<>(); + private static Map getAverageForEachNumericCriterion( + Map> estimationsMap, + Map> criteria, + List scales + ) { + Map> averages = new TreeMap<>(); for (Map.Entry> entry : estimationsMap.entrySet()) { for (TTExpertEstimationsModel ttExpertEstimationsModel : entry.getValue()) { for (TTCriteriaEstimationsModel ttCriteriaEstimationsModel : ttExpertEstimationsModel.getCriteria2Estimation()) { - if (!ttCriteriaEstimationsModel.getQualitative()) { - List averList = averages.get(ttCriteriaEstimationsModel.getCriteriaID()); - if (averList == null) { - averList = new ArrayList<>(); - } - averList.add(Integer.parseInt(ttCriteriaEstimationsModel.getEstimation().get(0))); - averages.put(ttCriteriaEstimationsModel.getCriteriaID(), averList); + TTCriteriaModel criterion = getCriterion(ttCriteriaEstimationsModel.getCriteriaID(), criteria); + TTScaleModel scale = getScale(ttCriteriaEstimationsModel.getScaleID(), scales); + + if (isQualitativeAssessment(criterion, ttCriteriaEstimationsModel) && + !isCrispQualitativeAssessment(scale)) { + // this is a 2-tuple that needs different pre-processing + continue; } + + List averList = averages.get(ttCriteriaEstimationsModel.getCriteriaID()); + if (averList == null) { + averList = new ArrayList<>(); + } + + Float valueToRemember; + if (scale != null && scale.getValues() != null) { + // this is a linguistic variable that has to be replaced with a numeric value + valueToRemember = findReplacingCrispLinguisticValue( + ttCriteriaEstimationsModel.getEstimation().get(0), + scale); + } else { + // this is a numeric value that should be parsed from string + valueToRemember = Float.parseFloat(ttCriteriaEstimationsModel.getEstimation().get(0)); + } + + averList.add(valueToRemember); + + averages.put(ttCriteriaEstimationsModel.getCriteriaID(), averList); } } } - for (Map.Entry> entry : averages.entrySet()) { - sumFinal.put(entry.getKey(), entry.getValue().stream().mapToInt(Integer::intValue).sum()); + + Map sumFinal = new TreeMap<>(); + for (Map.Entry> entry : averages.entrySet()) { + double normalized_value = Math.sqrt(entry.getValue().stream().mapToDouble(e -> Math.pow(e, 2)).sum()); + sumFinal.put(entry.getKey(), normalized_value); } return sumFinal; + } + + private static TTCriteriaModel getCriterion(String criterionID, Map> criteria) { + for (Map.Entry> entry : criteria.entrySet()) { + for (TTCriteriaModel criteriaModel : entry.getValue()) { + if (criteriaModel.getCriteriaID().equals(criterionID)) { + return criteriaModel; + } + } + } + return null; + } + + private static TTScaleModel getScale(String scaleID, List scales) { + for (TTScaleModel scaleModel : scales) { + if (scaleModel.getScaleID().equals(scaleID)) { + return scaleModel; + } + } + return null; + } + + private static Float findReplacingCrispLinguisticValue(String label, TTScaleModel scale) { + return scale.getValues().get(scale.getLabels().indexOf(label)); + } + + private static boolean isQualitativeAssessment(TTCriteriaModel criterion, + TTCriteriaEstimationsModel ttCriteriaEstimationsModel) { + // in new format qualitative flag is described in criteria + if (criterion != null && criterion.isQualitative()) { + return true; + } + // in old format qualitative flag is described in each assessment + return ttCriteriaEstimationsModel.getQualitative(); + } + private static boolean isCrispQualitativeAssessment(TTScaleModel scale) { + // in new format qualitative flag is described in criteria + return scale != null && scale.getValues() != null; } public static List getOrderedCriteriaList(Map> criteria, diff --git a/src/main/java/dss/lingvo/utils/models/input/TTCriteriaModel.java b/src/main/java/dss/lingvo/utils/models/input/TTCriteriaModel.java index 49899a0..d8f262a 100644 --- a/src/main/java/dss/lingvo/utils/models/input/TTCriteriaModel.java +++ b/src/main/java/dss/lingvo/utils/models/input/TTCriteriaModel.java @@ -4,6 +4,8 @@ public class TTCriteriaModel { private String criteriaID; private String criteriaName; private boolean qualitative; + private boolean isBenefit; + private String units; public String getCriteriaID() { return criteriaID; @@ -28,4 +30,20 @@ public boolean isQualitative() { public void setQualitative(boolean qualitative) { this.qualitative = qualitative; } + + public String getUnits() { + return units; + } + + public void setUnits(String units) { + this.units = units; + } + + public boolean isBenefit() { + return isBenefit; + } + + public void setBenefit(boolean isBenefit) { + this.isBenefit = isBenefit; + } } diff --git a/src/main/java/dss/lingvo/utils/models/input/TTScaleModel.java b/src/main/java/dss/lingvo/utils/models/input/TTScaleModel.java index db03f80..0410399 100644 --- a/src/main/java/dss/lingvo/utils/models/input/TTScaleModel.java +++ b/src/main/java/dss/lingvo/utils/models/input/TTScaleModel.java @@ -6,6 +6,7 @@ public class TTScaleModel { private String scaleID; private String scaleName; private List labels; + private List values; public List getLabels() { return labels; @@ -30,4 +31,12 @@ public String getScaleID() { public void setScaleID(String scaleID) { this.scaleID = scaleID; } + + public List getValues() { + return values; + } + + public void setValues(List values) { + this.values = values; + } } diff --git a/src/main/java/dss/lingvo/utils/models/input/common/TTCommonInputModel.java b/src/main/java/dss/lingvo/utils/models/input/common/TTCommonInputModel.java index 7bea906..730eadd 100644 --- a/src/main/java/dss/lingvo/utils/models/input/common/TTCommonInputModel.java +++ b/src/main/java/dss/lingvo/utils/models/input/common/TTCommonInputModel.java @@ -17,6 +17,8 @@ public class TTCommonInputModel { private Map abstractionLevelWeights; @JsonProperty("expertWeightsRule") private Map expertWeightsRule; + @JsonProperty("criteriaWeightsPerGroup") + private Map > criteriaWeightsPerGroup; public List getAlternatives() { return alternatives; @@ -73,4 +75,12 @@ public Map getExpertWeightsRule() { public void setExpertWeightsRule(Map expertWeightsRule) { this.expertWeightsRule = expertWeightsRule; } + + public Map> getCriteriaWeightsPerGroup() { + return criteriaWeightsPerGroup; + } + + public void setCriteriaWeightsPerGroup(Map>criteriaWeightsPerGroup) { + this.criteriaWeightsPerGroup = criteriaWeightsPerGroup; + } } diff --git a/src/main/resources/description_multilevel.json b/src/main/resources/description_multilevel.json index 5c9fb01..0cec03e 100644 --- a/src/main/resources/description_multilevel.json +++ b/src/main/resources/description_multilevel.json @@ -121,7 +121,7 @@ { "criteriaID": "К.ЭТУА.2", "criteriaName": "Справедливость с точки зрения рабочих", - "qualitative": true + "qualitative": false } ], "aesthetics": [ diff --git a/src/test/java/dss/lingvo/t2hflts/multilevel/TT2HFLTSMHTWOWAMultiLevelOperatorTest.java b/src/test/java/dss/lingvo/t2hflts/multilevel/TT2HFLTSMHTWOWAMultiLevelOperatorTest.java index 62a722e..7098303 100644 --- a/src/test/java/dss/lingvo/t2hflts/multilevel/TT2HFLTSMHTWOWAMultiLevelOperatorTest.java +++ b/src/test/java/dss/lingvo/t2hflts/multilevel/TT2HFLTSMHTWOWAMultiLevelOperatorTest.java @@ -41,7 +41,7 @@ public static void runOnceBeforeClass() throws IOException { .aggregateByAbstractionLevel(model.getCriteria(), model.getAbstractionLevels(), all, - targetScaleSize); + targetScaleSize, model.getCriteriaWeightsPerGroup()); allByExpert = tt2HFLTSMHTWOWAMultiLevelOperator .transposeByAbstractionLevel(model.getAbstractionLevels().size(), @@ -66,7 +66,7 @@ public void testAggregateByAbstractionLevel(){ assertEquals(26, allByLevel.get(0).size()); assertEquals(8, allByLevel.get(0).get(0).size()); ArrayList tmp = new ArrayList<>(); - tmp.add(new TTTuple("p",7,0.17411518f,1)); + tmp.add(new TTTuple("p",7,0.25560308f,1)); TT2HFLTS myHFLTS1 = new TT2HFLTS(tmp); assertEquals(myHFLTS1, allByLevel.get(0).get(0).get(0)); @@ -79,7 +79,7 @@ public void testAggregateByExpert(){ assertEquals(26, altToLevel.size()); assertEquals(8, altToLevel.get(0).size()); ArrayList tmp = new ArrayList<>(); - tmp.add(new TTTuple("p",7,0.17411518f,1)); + tmp.add(new TTTuple("p",7,0.25560308f,1)); TT2HFLTS myHFLTS1 = new TT2HFLTS(tmp); assertEquals(myHFLTS1, allByLevel.get(0).get(0).get(0)); @@ -93,7 +93,7 @@ public void testTransposeByAbstractionLevel(){ assertEquals(26, allByExpert.get(0).size()); assertEquals(7, allByExpert.get(0).get(0).size()); ArrayList tmp = new ArrayList<>(); - tmp.add(new TTTuple("p",7,0.17411518f,1)); + tmp.add(new TTTuple("p",7,0.25560308f,1)); TT2HFLTS myHFLTS1 = new TT2HFLTS(tmp); assertEquals(myHFLTS1, allByExpert.get(0).get(0).get(0)); @@ -105,7 +105,7 @@ public void testAggregateFinalAltEst(){ assertEquals(26, altVec.size()); ArrayList tmp = new ArrayList<>(); - tmp.add(new TTTuple("p",7,0.17411518f,1)); + tmp.add(new TTTuple("p",7,0.25560308f,1)); TT2HFLTS myHFLTS1 = new TT2HFLTS(tmp); assertEquals(myHFLTS1, allByExpert.get(0).get(0).get(0));