Sign in

    moonchoice

    Explainable bounded multi-criteria ranking and weight-scenario analysis

    mcda
    topsis
    vikor
    promethee
    decision-analysis
    Download zip
    Author
    Version
    0.1.0
    License
    MIT
    Last updated
    7 hours ago
    Downloads
    2

    #MoonChoice

    有界、可解释的 MoonBit 多准则决策分析(MCDA)库。输入已观测的有限备选项、收益/成本指标及权重,输出排序、逐指标证据和偏好敏感性;不替代人的决策。

    模块:wrMamba/moonchoice@0.1.0 · MIT · 无第三方运行时依赖(仅 MoonBit core)。

    #三类下游工作流

    • 采购与工程评审:读取混合量纲的 CSV,比较吞吐量、延迟、成本,得到 SAW/TOPSIS 排序及贡献明细,而不是一个不透明总分。
    • 部署配置系统:给定候选测量,用原始单位的 PROMETHEE 阈值形成成对偏好,检查净流量与 Pareto 层。
    • 数据分析工作流:通过显式权重网格重算,查看首位次数、秩频数、跨方法一致性、删项/删指标后的反转;通过版本化 JSON 嵌入其他应用。

    #安装

    安装官方 MoonBit 工具链后,在自己的模块目录运行:

    moon add wrMamba/moonchoice@0.1.0

    在 moon.pkg 中添加导入:

    import {
    "wrMamba/moonchoice" @choice,
    }

    fn main raise {
    let m = @choice.decision_matrix(
    ["A", "B", "C"], ["quality", "cost"],
    [@choice.Benefit, @choice.Cost],
    [[7.0, 8.0], [9.0, 10.0], [6.0, 5.0]],
    )
    let r = @choice.saw(m, [0.5, 0.5])
    assert_eq(r.ranking.winners(), ["B", "C"])
    println(@choice.ranking_json(r.ranking).stringify())
    }

    可执行包还需 pkgtype(kind: "executable")。完整独立导入例子见 examples/。

    #API 导览

    文件已实现内容
    matrix, normalization防御性复制、收益/成本、minmax/向量归一化、尺度安全权重
    saw, topsis, wpm加权和贡献、理想解距离轨迹、正数对数加权乘积
    vikorS/R/Q、可接受优势/稳定性、妥协集合
    preference, promethee六种偏好函数、成对矩阵、正负净流量
    objective_weights, ahp熵/离差/CRITIC、AHP 特征向量、残差/一致性/最坏三元组
    pareto, agreement精确支配层与见证、带并列的 Spearman/Kendall tau-b
    scenarios, explain, transform有限网格、解析 SAW 交点、成对解释、单位变换、删项反转
    diagnostics指标统计、删指标敏感性、含警告和独立 SAW 贡献的报告
    json_codec, csv_codec, service有界 JSON/CSV、版本模式、无 I/O 的 JSON 请求分派

    上述实现均为 .mbt。完整签名见 pkg.generated.mbti。失败使用 ChoiceError::{Invalid, Budget, Numerical},不填补缺失数据。

    #三个可运行例子

    moon run examples/procurement --target wasm-gc --deny-warn moon run examples/deployment --target wasm-gc --deny-warn moon run examples/scenarios --target wasm-gc --deny-warn

    每个例子真实导入、计算和断言,再输出 JSON;不是只打印文案。可换为 wasm、js,或在有 C 编译器的环境换成 native。

    #格式与请求接口

    CSV 第一行固定 alternative,<指标名...>,第二行固定 direction,<benefit|cost...>,后续为名称与数值。支持逗号、双引号转义、LF/CRLF 和带换行字段;不猜分隔符,不忽略空记录。数字为 JSON 数字语法,不接收 NaN/Infinity/空字段。

    矩阵 JSON:

    {"schema":"moonchoice.matrix/1","alternatives":["A","B"],"criteria":["speed"],"directions":["benefit"],"values":[[1],[2]]}

    dispatch(String) -> String 接收:

    {"schema":"moonchoice.request/1","operation":"rank","matrix":{"schema":"moonchoice.matrix/1","alternatives":["A","B"],"criteria":["speed"],"directions":["benefit"],"values":[[1],[2]]},"weights":[1],"algorithm":{"kind":"topsis","normalization":"minmax"}}

    operation 为 rank、report、scenarios;后者用二维 scenarios 替代 weights。算法为 saw/topsis(必须指定 normalization=minmax/vector)、product、vikor(必须指定 v)、promethee(必须指定每指标 preferences)。偏好如 {"kind":"linear","q":1,"p":5};另有 usual、ushape(q)、vshape(p)、level(q,p)、gaussian(sigma)。拒绝未知/不适用字段。响应包含 schema、ok 与 result 或分类 error。详见 架构说明。

    #数值和语义约定

    • 最多256个备选项、32个指标;标签唯一、非空白、<=128 UTF-16单元;输入有限且绝对值<=1e100。
    • 权重非负且至少一个正数,自动归一化。minmax常量列为零信息;向量成本列取负号,不采用倒数变换。
    • TOPSIS全同候选得0.5;VIKOR零跨度项为0;无信息客观赋权显式返回均匀回退;WPM要求原始值严格为正。
    • 先精确稳定排序,再按组首分数绝对容差(默认1e-10)形成competition rank。不是epsilon比较器,不保证链式近似相等可传递。
    • 单指标不支持连续权重交点,网格只允许权重1。Spearman/tau-b无秩方差返回None。
    • 最多16种比较方法、64组情景;情景预算 nnp*count<=16777216。JSON/CSV<=1 Mi UTF-16单元;JSON<=8层容器。
    • DecisionMatrix不暴露内部可变数组;返回结果数组可变,Ranking消费接口重校验一致性。不要修改报告后再当可信计算结果。
    • Double舍入和下溢仍存在,极小权重/相对差异可能丢失;不提供任意精度或跨平台逐位一致保证。

    不做投票/IRV、稳定匹配、连续规划、预测训练、缺失值填补、随机概率推断或自动执行采购/医疗/金融决策。情景频数不是获胜概率。归一化依赖候选集合,删项可能反转排序,库如实报告而不宣称消除。

    #开发和验收

    moon info && moon fmt moon fmt --check moon check --deny-warn moon test --deny-warn moon build --deny-warn python scripts/audit.py

    GitHub Actions 对 wasm-gc、wasm、js、native 执行接口一致性、严格检查、测试、构建和三个工作流。审计脚本列出生产文件并保守统计有效提交;测试、示例、接口文件不计核心行数。再生成数值对照见 参考与验证。不提供未经实测的性能基准或绝对唯一性承诺。语义查重记录。

    ChoiceError

    pub suberror ChoiceError {
    Invalid(String)
    Budget(String)
    Numerical(String)
    } derive(
    Debug
    )

    AdditiveBreakEven

    pub(all) struct AdditiveBreakEven {
    intercept : Double
    slope : Double
    crossing : Double?
    tied_for_all_weights : Bool
    } derive(
    Debug
    )

    AdditiveResult

    pub(all) struct AdditiveResult {
    ranking : Ranking
    normalized : Array[Array[Double]]
    contributions : Array[Array[Double]]
    weights : Array[Double]
    } derive(
    Debug
    )

    AhpResult

    pub(all) struct AhpResult {
    weights : Array[Double]
    lambda_max : Double
    consistency_index : Double
    consistency_ratio : Double
    residual : Double
    iterations : Int
    worst_triad : Array[Int]
    worst_log_inconsistency : Double
    } derive(
    Debug
    )

    CriterionComparison

    pub(all) struct CriterionComparison {
    criterion : String
    left_value : Double
    right_value : Double
    oriented_difference : Double
    weighted_preference_left : Double
    weighted_preference_right : Double
    } derive(
    Debug
    )

    CriterionProfile

    pub(all) struct CriterionProfile {
    name : String
    direction : Direction
    minimum : Double
    maximum : Double
    mean : Double
    population_deviation : Double
    distinct_values : Int
    constant : Bool
    normalized_weight : Double
    } derive(
    Debug
    )

    CriterionRemoval

    pub(all) struct CriterionRemoval {
    omitted : String
    remaining_criteria : Array[String]
    ranking : Ranking
    agreement : RankAgreement
    winner_changed : Bool
    } derive(
    Debug
    )

    CriterionSensitivity

    pub(all) struct CriterionSensitivity {
    baseline : Ranking
    removals : Array[CriterionRemoval]
    skipped_zero_weight_remainders : Array[String]
    } derive(
    Debug
    )

    DecisionMatrix

    pub struct DecisionMatrix {
    names : Array[String]
    criteria : Array[String]
    directions : Array[Direction]
    cells : Array[Array[Double]]
    }

    Validated immutable matrix; all array accessors return defensive copies.

    DecisionMatrix::alternatives

    fn DecisionMatrix::alternatives(self : DecisionMatrix) -> Array[String]

    DecisionMatrix::cell

    fn DecisionMatrix::cell(self : DecisionMatrix, row : Int, column : Int) -> Double raise ChoiceError

    DecisionMatrix::column

    fn DecisionMatrix::column(self : DecisionMatrix, column : Int) -> Array[Double] raise ChoiceError

    DecisionMatrix::convert_units

    fn DecisionMatrix::convert_units(self : DecisionMatrix, scales : Array[Double], offsets : Array[Double]) -> DecisionMatrix raise ChoiceError

    Affine unit conversion must be strictly increasing; directions stay unchanged.

    DecisionMatrix::criterion_directions

    fn DecisionMatrix::criterion_directions(self : DecisionMatrix) -> Array[Direction]

    DecisionMatrix::criterion_names

    fn DecisionMatrix::criterion_names(self : DecisionMatrix) -> Array[String]

    DecisionMatrix::normalize

    fn DecisionMatrix::normalize(self : DecisionMatrix, mode : Normalization) -> Array[Array[Double]] raise ChoiceError

    Benefit-oriented values. Vector-normalized cost criteria are negated.

    DecisionMatrix::select_alternatives

    fn DecisionMatrix::select_alternatives(self : DecisionMatrix, indices : Array[Int]) -> DecisionMatrix raise ChoiceError

    DecisionMatrix::select_criteria

    fn DecisionMatrix::select_criteria(self : DecisionMatrix, indices : Array[Int]) -> DecisionMatrix raise ChoiceError

    DecisionMatrix::shape

    fn DecisionMatrix::shape(self : DecisionMatrix) -> (Int, Int)

    DecisionMatrix::to_json

    fn DecisionMatrix::to_json(self : DecisionMatrix) -> Json

    DecisionMatrix::values

    fn DecisionMatrix::values(self : DecisionMatrix) -> Array[Array[Double]]

    DecisionReport

    pub(all) struct DecisionReport {
    ranking : Ranking
    weights : Array[Double]
    profiles : Array[CriterionProfile]
    pareto : ParetoResult
    additive_contributions : Array[Array[Double]]
    warnings : Array[String]
    } derive(
    Debug
    )

    Direction

    pub(all) enum Direction {
    Benefit
    Cost
    } derive(Eq,
    Debug
    )

    Direction::equal

    fn Direction::equal(Direction, Direction) -> Bool

    Direction::not_equal

    fn Direction::not_equal(x : Direction, y : Direction) -> Bool

    Method

    pub(all) enum Method {
    Additive(Normalization)
    IdealDistance(Normalization)
    Product
    Compromise(Double)
    Outranking(Array[Preference])
    } derive(
    Debug
    )

    Method::to_repr

    MethodComparison

    pub(all) struct MethodComparison {
    rankings : Array[Ranking]
    agreements : Array[Array[RankAgreement]]
    } derive(
    Debug
    )

    Normalization

    pub(all) enum Normalization {
    MinMax
    Vector
    } derive(Eq,
    Debug
    )

    Normalization::equal

    Normalization::not_equal

    fn Normalization::not_equal(x : Normalization, y : Normalization) -> Bool

    PairExplanation

    pub(all) struct PairExplanation {
    left : String
    right : String
    criteria : Array[CriterionComparison]
    aggregate_left : Double
    aggregate_right : Double
    net_preference : Double
    left_dominates : Bool
    right_dominates : Bool
    } derive(
    Debug
    )

    ParetoResult

    pub(all) struct ParetoResult {
    fronts : Array[Array[String]]
    front_indices : Array[Int]
    dominated_by : Array[Array[Int]]
    } derive(
    Debug
    )

    Preference

    pub(all) enum Preference {
    Usual
    UShape(Double)
    VShape(Double)
    Level(Double, Double)
    Linear(Double, Double)
    Gaussian(Double)
    } derive(
    Debug
    )

    ProductResult

    pub(all) struct ProductResult {
    ranking : Ranking
    log_contributions : Array[Array[Double]]
    relative_products : Array[Double]
    } derive(
    Debug
    )

    PrometheeResult

    pub(all) struct PrometheeResult {
    ranking : Ranking
    preference_matrix : Array[Array[Double]]
    positive_flow : Array[Double]
    negative_flow : Array[Double]
    } derive(
    Debug
    )

    RankAgreement

    pub(all) struct RankAgreement {
    spearman : Double?
    kendall_tau_b : Double?
    concordant_pairs : Int
    discordant_pairs : Int
    shared_winners : Array[String]
    } derive(
    Debug
    )

    Ranking

    pub(all) struct Ranking {
    labels : Array[String]
    scores : Array[Double]
    order : Array[Int]
    ranks : Array[Int]
    higher_is_better : Bool
    tie_tolerance : Double
    } derive(
    Debug
    )

    Ranking::to_repr

    Ranking::top

    fn Ranking::top(self : Ranking, count : Int) -> Array[String] raise ChoiceError

    Ranking::winners

    fn Ranking::winners(self : Ranking) -> Array[String] raise ChoiceError

    RemovalAnalysis

    pub(all) struct RemovalAnalysis {
    removed : String
    baseline : Ranking
    remaining : Ranking
    agreement : RankAgreement
    winner_changed : Bool
    } derive(
    Debug
    )

    ScenarioReport

    pub(all) struct ScenarioReport {
    rankings : Array[Ranking]
    rank_counts : Array[Array[Int]]
    winner_counts : Array[Int]
    mean_ranks : Array[Double]
    best_ranks : Array[Int]
    worst_ranks : Array[Int]
    universally_best : Array[String]
    pairwise_wins : Array[Array[Int]]
    pairwise_ties : Array[Array[Int]]
    } derive(
    Debug
    )

    TopsisResult

    pub(all) struct TopsisResult {
    ranking : Ranking
    normalized : Array[Array[Double]]
    weighted : Array[Array[Double]]
    ideal : Array[Double]
    anti_ideal : Array[Double]
    distance_ideal : Array[Double]
    distance_anti_ideal : Array[Double]
    } derive(
    Debug
    )

    VikorResult

    pub(all) struct VikorResult {
    ranking : Ranking
    utility : Array[Double]
    regret : Array[Double]
    criterion_regrets : Array[Array[Double]]
    compromise : Array[String]
    acceptable_advantage : Bool
    acceptable_stability : Bool
    } derive(
    Debug
    )

    WeightEstimate

    pub(all) struct WeightEstimate {
    weights : Array[Double]
    information : Array[Double]
    uniform_fallback : Bool
    } derive(
    Debug
    )

    additive_break_even

    fn additive_break_even(m : DecisionMatrix, weights : Array[Double], criterion : Int, left : Int, right : Int) -> AdditiveBreakEven raise ChoiceError

    Exact SAW one-coordinate weight-grid crossing for a fixed normalization matrix.

    ahp

    fn ahp(comparisons : Array[Array[Double]], tolerance? : Double, max_iterations? : Int) -> AhpResult raise ChoiceError

    Reciprocal positive matrices of size 1..15; bounded power iteration.

    compare_methods

    fn compare_methods(m : DecisionMatrix, weights : Array[Double], methods : Array[Method], tolerance? : Double) -> MethodComparison raise ChoiceError

    criterion_profiles

    fn criterion_profiles(m : DecisionMatrix, weights : Array[Double]) -> Array[CriterionProfile] raise ChoiceError

    Observed ranges and scaled population deviations, not confidence estimates.

    criterion_sensitivity

    fn criterion_sensitivity(m : DecisionMatrix, weights : Array[Double], algorithm : Method) -> CriterionSensitivity raise ChoiceError

    Recomputes each leave-one-criterion-out model; zero remaining weight is recorded.

    critic_weights

    fn critic_weights(m : DecisionMatrix) -> WeightEstimate raise ChoiceError

    decision_matrix

    fn decision_matrix(alternatives : Array[String], criteria : Array[String], directions : Array[Direction], values : Array[Array[Double]]) -> DecisionMatrix raise ChoiceError

    decision_report

    fn decision_report(m : DecisionMatrix, weights : Array[Double], algorithm : Method) -> DecisionReport raise ChoiceError

    Auxiliary additive contributions are a separate diagnostic, not every method's score.

    decode_csv

    fn decode_csv(text : String) -> DecisionMatrix raise ChoiceError

    Header alternative,; second record direction,<benefit|cost>.

    decode_matrix

    fn decode_matrix(text : String) -> DecisionMatrix raise ChoiceError

    deviation_weights

    fn deviation_weights(m : DecisionMatrix) -> WeightEstimate raise ChoiceError

    Population dispersion in benefit-oriented minmax units, not physical units.

    dispatch

    fn dispatch(request : String) -> String

    Portable string-in/string-out dispatcher. No filesystem, network or global state.

    encode_csv

    fn encode_csv(m : DecisionMatrix) -> String

    encode_matrix

    fn encode_matrix(m : DecisionMatrix) -> String

    entropy_weights

    fn entropy_weights(m : DecisionMatrix) -> WeightEstimate raise ChoiceError

    Entropy of benefit-oriented minmax columns; all-zero columns are uninformative.

    evaluate

    fn evaluate(m : DecisionMatrix, weights : Array[Double], algorithm : Method, tolerance? : Double) -> Ranking raise ChoiceError

    explain_pair

    fn explain_pair(m : DecisionMatrix, weights : Array[Double], preferences : Array[Preference], left : Int, right : Int) -> PairExplanation raise ChoiceError

    normalize_weights

    fn normalize_weights(weights : Array[Double]) -> Array[Double] raise ChoiceError

    pareto_fronts

    fn pareto_fronts(m : DecisionMatrix) -> ParetoResult

    Exact dominance, intentionally without tolerance that could break transitivity.

    parse_document

    fn parse_document(text : String) -> Json raise ChoiceError

    Bounded parse: at most 1 Mi code units, with at most eight nested containers.

    preference_degree

    fn preference_degree(pref : Preference, difference : Double) -> Double raise ChoiceError

    promethee

    fn promethee(m : DecisionMatrix, weights : Array[Double], preferences : Array[Preference], tolerance? : Double) -> PrometheeResult raise ChoiceError

    Thresholds are in raw criterion units, not normalized units.

    rank_agreement

    fn rank_agreement(left : Ranking, right : Ranking) -> RankAgreement raise ChoiceError

    rank_scores

    fn rank_scores(labels : Array[String], scores : Array[Double], higher_is_better? : Bool, tolerance? : Double) -> Ranking raise ChoiceError

    Stable exact sort, followed by anchor-based competition-rank tie groups.

    ranking_json

    fn ranking_json(ranking : Ranking) -> Json raise ChoiceError

    Validates externally mutable Ranking arrays before serializing them.

    removal_analysis

    fn removal_analysis(m : DecisionMatrix, weights : Array[Double], algorithm : Method, removed : Int) -> RemovalAnalysis raise ChoiceError

    Reports actual rank reversal when removing an alternative changes normalization.

    report_json

    fn report_json(report : DecisionReport) -> Json raise ChoiceError

    saw

    fn saw(m : DecisionMatrix, weights : Array[Double], normalization? : Normalization, tolerance? : Double) -> AdditiveResult raise ChoiceError

    topsis

    fn topsis(m : DecisionMatrix, weights : Array[Double], normalization? : Normalization, tolerance? : Double) -> TopsisResult raise ChoiceError

    vikor

    fn vikor(m : DecisionMatrix, weights : Array[Double], v? : Double, tolerance? : Double) -> VikorResult raise ChoiceError

    weight_grid

    fn weight_grid(weights : Array[Double], criterion : Int, values : Array[Double]) -> Array[Array[Double]] raise ChoiceError

    Vary one weight, redistributing the rest proportionally to baseline weights.

    weight_scenarios

    fn weight_scenarios(m : DecisionMatrix, scenarios : Array[Array[Double]], algorithm : Method, tolerance? : Double) -> ScenarioReport raise ChoiceError

    Frequencies describe only the supplied finite scenario grid, not probabilities.

    weighted_product

    fn weighted_product(m : DecisionMatrix, weights : Array[Double], tolerance? : Double) -> ProductResult raise ChoiceError

    Scores are log products; relative_products are exp(score - best_score).