Skip to main content

decision_making_core 0.1.0 copy "decision_making_core: ^0.1.0" to clipboard
decision_making_core: ^0.1.0 copied to clipboard

Pure Dart multi-criteria decision-making (MCDM) core library implementing AHP, SAW, and TOPSIS algorithms.

decision_making_core #

A pure Dart multi-criteria decision-making (MCDM) library implementing SAW, TOPSIS, and AHP algorithms.

  • Zero Flutter dependencies: Can be used in CLI tools, background workers, backend microservices, or frontend applications.
  • Synchronous & fast: Pure computational engine without Futures or event loop overhead.
  • Strictly tested: Validated with golden datasets under a numerical tolerance of 1e-4.

Installation #

Add decision_making_core to your pubspec.yaml:

dependencies:
  decision_making_core: ^0.1.0

Usage Examples #

1. Simple Additive Weighting (SAW) #

import 'package:decision_making_core/decision_making_core.dart';

void main() {
  final alternatives = [
    WeightedDecisionAlternative(id: 'a1', name: 'Alternative 1'),
    WeightedDecisionAlternative(id: 'a2', name: 'Alternative 2'),
  ];

  final criteria = [
    WeightedDecisionCriteria(
      id: 'c1',
      name: 'Cost',
      weightPercent: 40,
      isBenefit: false, // Cost criteria
      maxValue: 100,
    ),
    WeightedDecisionCriteria(
      id: 'c2',
      name: 'Quality',
      weightPercent: 60,
      isBenefit: true,  // Benefit criteria
      maxValue: 100,
    ),
  ];

  final matrix = [
    WeightedDecisionMatrix(
      id: 'm1',
      alternative: alternatives[0],
      ratings: [
        WeightedDecisionRating(id: 'r1_1', criteria: criteria[0], value: 40),
        WeightedDecisionRating(id: 'r1_2', criteria: criteria[1], value: 80),
      ],
    ),
    WeightedDecisionMatrix(
      id: 'm2',
      alternative: alternatives[1],
      ratings: [
        WeightedDecisionRating(id: 'r2_1', criteria: criteria[0], value: 60),
        WeightedDecisionRating(id: 'r2_2', criteria: criteria[1], value: 90),
      ],
    ),
  ];

  final results = SawCalculator.calculate(matrix);

  for (final res in results) {
    print('Rank ${res.rank}: ${res.alternative.name} (Score: ${res.score.toStringAsFixed(4)})');
  }

  // To inspect all intermediate matrices and normalized weights:
  // final detailed = SawCalculator.calculateDetailed(matrix);
  // print(detailed.normalizedWeights);
  // print(detailed.toJson());
}

2. TOPSIS (Similarity to Ideal Solution) #

import 'package:decision_making_core/decision_making_core.dart';

void main() {
  final criteria = [
    WeightedDecisionCriteria(
      id: 'c1',
      name: 'Price',
      weightPercent: 50,
      isBenefit: false,
      maxValue: 100,
    ),
    WeightedDecisionCriteria(
      id: 'c2',
      name: 'Quality',
      weightPercent: 50,
      isBenefit: true,
      maxValue: 100,
    ),
  ];

  final alternatives = [
    WeightedDecisionAlternative(id: 'a1', name: 'Vendor A'),
    WeightedDecisionAlternative(id: 'a2', name: 'Vendor B'),
  ];

  final matrix = [
    WeightedDecisionMatrix(
      id: 'm1',
      alternative: alternatives[0],
      ratings: [
        WeightedDecisionRating(id: 'r1', criteria: criteria[0], value: 30),
        WeightedDecisionRating(id: 'r2', criteria: criteria[1], value: 80),
      ],
    ),
    WeightedDecisionMatrix(
      id: 'm2',
      alternative: alternatives[1],
      ratings: [
        WeightedDecisionRating(id: 'r3', criteria: criteria[0], value: 50),
        WeightedDecisionRating(id: 'r4', criteria: criteria[1], value: 60),
      ],
    ),
  ];

  final rawMatrix = TopsisRawMatrix(criterias: criteria, matrixs: matrix);
  final results = TopsisCalculator.calculate(rawMatrix);

  for (final res in results) {
    print('Rank ${res.rank}: ${res.alternative.name} (Score: ${res.score.toStringAsFixed(4)})');
  }

  // To inspect all intermediate matrices, ideal solutions, and separation measures:
  // final detailed = TopsisCalculator.calculateDetailed(rawMatrix);
  // print(detailed.positiveIdeal);
  // print(detailed.separationPlus);
  // print(detailed.toJson());
}

3. Analytic Hierarchy Process (AHP) #

import 'package:decision_making_core/decision_making_core.dart';

void main() {
  final criteria = [
    AhpItem(id: 'c1', name: 'Cost'),
    AhpItem(id: 'c2', name: 'Quality'),
  ];

  final alternatives = [
    AhpItem(id: 'a1', name: 'Option A'),
    AhpItem(id: 'a2', name: 'Option B'),
  ];

  final hierarchy = AhpCalculator.generateHierarchy(criteria, alternatives);

  // Criteria pairwise comparison (Cost is 2x more important than Quality)
  final criteriaInputs = [
    PairwiseComparisonInput(
      id: 'p1',
      left: criteria[0],
      right: criteria[1],
      preferenceValue: 2,
      isLeftMoreImportant: true,
    ),
  ];

  // Alternatives pairwise comparisons under each criterion
  final alternativeInputs = [
    PairwiseAlternativeInput(
      criteria: criteria[0],
      alternative: [
        PairwiseComparisonInput(
          id: 'alt_cost',
          left: alternatives[0],
          right: alternatives[1],
          preferenceValue: 3,
          isLeftMoreImportant: true,
        ),
      ],
    ),
    PairwiseAlternativeInput(
      criteria: criteria[1],
      alternative: [
        PairwiseComparisonInput(
          id: 'alt_quality',
          left: alternatives[0],
          right: alternatives[1],
          preferenceValue: 2,
          isLeftMoreImportant: false,
        ),
      ],
    ),
  ];

  final result = AhpCalculator.calculate(
    hierarchy: hierarchy,
    inputsCriteria: criteriaInputs,
    inputsAlternative: alternativeInputs,
  );

  print('Criteria Consistent: ${result.isConsistentCriteria} (CR: ${result.consistencyCriteriaRatio})');
  for (final detail in result.results!) {
    print('${detail.name}: ${detail.value.toStringAsFixed(4)}');
  }

  // To inspect all intermediate comparison matrices, weights, and consistency metrics:
  // final detailed = AhpCalculator.calculateDetailed(
  //   hierarchy: hierarchy,
  //   inputsCriteria: criteriaInputs,
  //   inputsAlternative: alternativeInputs,
  // );
  // print(detailed.criteriaWeights);
  // print(detailed.toJson());
}

Sensitivity Analysis & Rank Reversal Detection #

The library provides automated weight sensitivity analysis and rank reversal detection for SAW (SawCalculator.analyzeSensitivity) and TOPSIS (TopsisCalculator.analyzeSensitivity).

Note: This feature detects rank reversal phenomena; it does not prevent them. Rank reversal is an inherent mathematical characteristic of multi-criteria decision making methods where relative rankings naturally shift due to changing normalization boundaries or ideal reference points when alternatives are added or removed.

Definitions #

  1. Weight Sensitivity:

    • Evaluates systematic +/- delta (default 10% relative, delta: 0.10) perturbations across each criterion in deterministic order (input criteria order, then +, then -).
    • Formula: w_k' = w_k * (1 +/- delta), while all other criteria weights are rescaled by (1 - w_k') / (1 - w_k) so the total weight strictly equals 1.0.
    • If w_k' >= 1.0, w_k' <= 0.0, or only 1 criterion exists, the case is safely skipped and its reason recorded in skipReason.
  2. Pairwise Ranking Changes:

    • Order change is evaluated strictly pairwise between every alternative pair (i, j): order is considered "changed" if the sign of (score_i - score_j) flips and the absolute difference on both sides is > 1e-9.
    • Score differences <= 1e-9 are treated as ties and do not trigger order change.
  3. Rank Reversal Detection:

    • For datasets with n >= 3 alternatives, each alternative r is removed one by one, scores are recalculated, and the relative ordering among remaining alternatives is compared to their original baseline order.
    • For datasets with n < 3, rank reversal testing is skipped and documented in skipReason.
  4. Stability Summary (isStable):

    • Returns true if and only if no ranking changes occur across all sensitivity perturbation cases and no rank reversals occur upon any alternative removal.

Caution & Caveats: Sensitivity and rank reversal behaviors depend directly on the chosen delta parameter and the mathematical normalization technique (e.g. min/max boundary normalization in SAW vs Euclidean vector normalization in TOPSIS). Rank reversals can naturally occur in multi-criteria methods when an ideal point or normalization denominator shifts upon alternative removal.

Example: Sensitivity Analysis (SAW & TOPSIS) #

import 'package:decision_making_core/decision_making_core.dart';

void main() {
  // SAW Sensitivity Analysis
  final sawResult = SawCalculator.analyzeSensitivity(matrix, delta: 0.1);
  print('SAW isStable: ${sawResult.isStable}');
  for (final sCase in sawResult.sensitivityCases) {
    if (sCase.hasOrderChanged) {
      print('Criterion ${sCase.criterionId} ${sCase.direction.symbol}: order changed!');
      for (final pair in sCase.swappedPairs) {
        print('  Swapped pair: ${pair.firstId} <-> ${pair.secondId}');
      }
    }
  }

  // TOPSIS Sensitivity Analysis
  final topsisResult = TopsisCalculator.analyzeSensitivity(rawMatrix, delta: 0.1);
  print('TOPSIS isStable: ${topsisResult.isStable}');
  for (final rCase in topsisResult.rankReversalCases) {
    if (rCase.hasReversal) {
      print('Rank reversal when removing ${rCase.removedAlternativeId}!');
      for (final pair in rCase.reversedPairs) {
        print('  Inverted relative order: ${pair.firstId} <-> ${pair.secondId}');
      }
    }
  }
}

Existing Behavior & Edge Cases #

The core calculation engines maintain consistent mathematical specifications for edge cases:

  • SAW cost criteria with value 0: If a cost criterion receives a rating value of 0.0, an explicit Exception("Rating value cannot be 0 for cost criteria") is thrown to prevent division by zero (min / 0.0) or infinite scores.
  • TOPSIS all-zero column: When all alternatives have a value of 0.0 for a criterion (resulting in Euclidean norm = 0.0), each normalized cell safely evaluates to 0.0 instead of NaN or divide-by-zero.
  • TOPSIS equidistant alternatives (D+ = D- = 0): When all alternatives have identical distance to ideal solutions (such as single-alternative datasets or identical rows where D+ = 0.0 and D- = 0.0), the relative closeness formula D- / (D+ + D-) safely resolves to a neutral score of 0.5 instead of NaN.

License #

MIT License. See LICENSE for details.

1
likes
0
points
0
downloads

Publisher

unverified uploader

Weekly Downloads

Pure Dart multi-criteria decision-making (MCDM) core library implementing AHP, SAW, and TOPSIS algorithms.

Repository (GitHub)
View/report issues

Topics

#mcdm #decision-support-system #ahp #topsis

License

unknown (license)

Dependencies

meta

More

Packages that depend on decision_making_core