Skip to main content

narust_158/inference/functions/
budget_functions.rs

1//! 🎯复刻OpenNARS `nars.inference.BudgetFunctions`
2
3use crate::{
4    debug_assert_matches,
5    entity::*,
6    global::*,
7    inference::{Budget, Truth},
8    language::Term,
9};
10
11/// 预算函数
12/// * 🚩【2024-05-03 14:48:13】现在仍依照OpenNARS原意「直接创建新值」
13///   * 📝本身复制值也没多大性能损耗
14///   * 📌「直接创建新值」会更方便后续调用
15///     * 📄减少无谓的`.clone()`
16/// * ❌【2024-06-24 16:30:56】不能使用`impl Budget + Sized`
17///   * 📝这会碰上生命周期问题:不能保证返回的值一定不包含对传入参数的借用
18///   * 📄首次错误出现位置:[`crate::inference::BudgetInference::merge_from`]
19///
20/// * ⚠️【2024-06-20 19:56:05】此处仅存储「纯函数」:不在其中修改传入量的函数
21pub trait BudgetFunctions: Budget {
22    /* ----------------------- Belief evaluation ----------------------- */
23
24    /// 模拟`BudgetFunctions.truthToQuality`
25    ///
26    /// # 📄OpenNARS
27    ///
28    /// Determine the quality of a judgement by its truth value alone
29    ///
30    /// Mainly decided by confidence, though binary judgement is also preferred
31    ///
32    /// @param t The truth value of a judgement
33    /// @return The quality of the judgement, according to truth value only
34    fn truth_to_quality(truth: &impl Truth) -> ShortFloat {
35        // * 🚩现在从更原始(无需反复转换)的`_float`函数中来
36        ShortFloat::from_float(Self::truth_to_quality_float(truth))
37    }
38    fn truth_to_quality_float(truth: &impl Truth) -> Float {
39        // * 🚩真值⇒质量:期望与「0.75(1-期望)」的最大值
40        // * 📝函数:max(c * (f - 0.5) + 0.5, 0.375 - 0.75 * c * (f - 0.5))
41        // * 📍最小值:当exp=3/7时,全局最小值为3/7(max的两端相等)
42        // * 🔑max(x,y) = (x+y+|x-y|)/2
43        let exp = truth.expectation();
44        exp.max((1.0 - exp) * 0.75)
45    }
46
47    /// 模拟`BudgetFunctions.rankBelief`
48    /// * 🚩🆕【2024-05-03 21:46:17】仅传入「语句」中的「真值」与「时间戳长度」,而非「语句」本身
49    ///   * 🚩`judgement.getTruth()` => `truth`
50    ///   * 🚩`judgement.getStamp().length()` => `stamp_len`
51    /// * 📝在使用该函数返回值的地方,仅为「比较大小」
52    ///   * 但[`ShortFloat`]已经实现了[`Ord`]并且需要[`UtilityFunctions::or`]
53    ///
54    /// # 📄OpenNARS
55    ///
56    /// Determine the rank of a judgement by its quality and originality (stamp length), called from Concept
57    ///
58    /// @param judgement The judgement to be ranked
59    /// @return The rank of the judgement, according to truth value only
60    fn rank_belief(judgement: &impl Judgement) -> Float {
61        // * 🚩两个指标:信度 + 原创性(时间戳长度)
62        // * 📝与信度正相关,与「时间戳长度」负相关;二者有一个好,那就整体好
63        let confidence = judgement.confidence();
64        let originality =
65            ShortFloat::from_float(1.0 / (judgement.evidence_length() as Float + 1.0));
66        (confidence | originality).to_float()
67    }
68
69    /* ----- Functions used both in direct and indirect processing of tasks ----- */
70
71    /// 概念的「总体优先级」
72    /// * 📝用于概念的「激活」函数上
73    /// Recalculate the quality of the concept [to be refined to show extension/intension balance]
74    fn concept_total_quality(concept: &Concept) -> ShortFloat {
75        // * 🚩计算所有词项链的「平均优先级」
76        let link_priority = concept.term_links_average_priority();
77        let link_priority = ShortFloat::from_float(link_priority);
78        // * 🚩词项复杂性指标:自身复杂性倒数
79        let term_complexity_factor = 1.0 / concept.term().complexity() as Float;
80        let term_complexity_factor = ShortFloat::from_float(term_complexity_factor);
81        // * 🚩总体:任意更大就行;结构简单的基本总是最好的;词项越复杂,质量下限越低
82        // * 📝计算方法:扩展逻辑或
83        link_priority | term_complexity_factor
84    }
85
86    /// # 📄OpenNARS
87    ///
88    /// Evaluate the quality of the judgment as a solution to a problem
89    /// * ⚠️这个返回值必须在0~1之间
90    fn solution_quality(query: &impl Sentence, solution: &impl Judgement) -> ShortFloat {
91        // * 🚩根据「一般疑问 | 特殊疑问/目标」拆解
92        // * 📝一般疑问 ⇒ 解の信度
93        // * 📝特殊疑问 ⇒ 解の期望 / 解の复杂度
94        let has_query_var = query.content().contain_var_q();
95        match has_query_var {
96            // * 🚩【特殊疑问/目标】 "what" question or goal
97            true => ShortFloat::from_float(
98                solution.expectation() / solution.content().complexity() as Float,
99            ),
100            // * 🚩【一般疑问】 "yes/no" question
101            false => solution.confidence(),
102        }
103    }
104
105    /// 模拟`BudgetFunctions.solutionEval`
106    /// * ✅【2024-06-23 01:37:36】目前已按照改版OpenNARS设置
107    ///
108    /// # 📄OpenNARS
109    ///
110    /// Evaluate the quality of a belief as a solution to a problem, then reward
111    /// the belief and de-prioritize the problem
112    ///
113    /// @param problem  The problem (question or goal) to be solved
114    /// @param solution The belief as solution
115    /// @param task     The task to be immediately processed, or null for continued
116    ///                 process
117    /// @return The budget for the new task which is the belief activated, if
118    ///         necessary
119    fn solution_eval(
120        problem: &impl Question,
121        solution: &impl Judgement,
122        question_task_budget: &impl Budget,
123    ) -> BudgetValue {
124        /* 📄OpenNARS改版:
125        final float newP = or(questionTaskBudget.getPriority(), solutionQuality(problem, solution));
126        final float newD = questionTaskBudget.getDurability();
127        final float newQ = truthToQuality(solution);
128        return new BudgetValue(newP, newD, newQ); */
129        // * ️📝新优先级 = 任务优先级 | 解决方案质量
130        let p = question_task_budget.priority() | Self::solution_quality(problem, solution);
131        // * 📝新耐久度 = 任务耐久度
132        let d = question_task_budget.durability();
133        // * ️📝新质量 = 解决方案の真值→质量
134        let q = Self::truth_to_quality(solution);
135        // 返回
136        BudgetValue::new(p, d, q)
137    }
138
139    /// 统一的「修正规则」预算函数
140    /// * 🚩依照改版OpenNARS,从旧稿中重整
141    /// * ✅完全脱离「推理上下文」仅有纯粹的「真值/预算值」计算
142    /// * ✅其中对「任务链可空性=信念链可空性」做断言:`feedBackToLinks == current_links_budget.is_some()`
143    fn revise(
144        new_belief_truth: &impl Truth, // from task
145        old_belief_truth: &impl Truth, // from belief
146        revised_truth: &impl Truth,
147        current_task_budget: &impl Budget,
148        current_links_budget: Option<(&impl Budget, &impl Budget)>,
149    ) -> ReviseResult {
150        // * 🚩计算落差 | t = task, b = belief
151        let dif_to_new_task =
152            ShortFloat::from_float(revised_truth.expectation_abs_dif(new_belief_truth));
153        let dif_to_old_belief =
154            ShortFloat::from_float(revised_truth.expectation_abs_dif(old_belief_truth));
155        // * 🚩若有:反馈到 [任务链, 信念链]
156        let new_links_budget = current_links_budget.map(|(t_budget, b_budget)| {
157            [
158                // * 📝当前任务链 降低预算:
159                // * * p = link & !difT
160                // * * d = link & !difT
161                // * * q = link
162                BudgetValue::new(
163                    t_budget.priority() & !dif_to_new_task,
164                    t_budget.durability() & !dif_to_new_task,
165                    t_budget.quality(),
166                ),
167                // * 📝当前信念链 降低预算:
168                // * * p = link & !difB
169                // * * d = link & !difB
170                // * * q = link
171                BudgetValue::new(
172                    b_budget.priority() & !dif_to_old_belief,
173                    b_budget.durability() & !dif_to_old_belief,
174                    b_budget.quality(),
175                ),
176            ]
177        });
178        // * 🚩用落差降低优先级、耐久度
179        // * 📝当前任务 降低预算:
180        // * * p = task & !difT
181        // * * d = task & !difT
182        // * * q = task
183        let new_task_budget = BudgetValue::new(
184            current_task_budget.priority() & !dif_to_new_task,
185            current_task_budget.durability() | !dif_to_new_task,
186            current_task_budget.quality(),
187        );
188        // * 🚩用更新后的值计算新差 | ❓此时是否可能向下溢出?
189        // * 📝新差 = 修正后信念.信度 - max(新信念.信度, 旧信念.信度)
190        let dif = revised_truth.confidence()
191            - old_belief_truth
192                .confidence()
193                .max(old_belief_truth.confidence());
194        // * 🚩计算新预算值
195        // * 📝优先级 = 差 | 当前任务
196        // * 📝耐久度 = (差 + 当前任务) / 2
197        // * 📝质量 = 新真值→质量
198        let new_budget = BudgetValue::new(
199            dif | current_task_budget.priority(),
200            ShortFloat::arithmetical_average([dif, current_task_budget.durability()]),
201            Self::truth_to_quality(revised_truth),
202        );
203        // 返回
204        ReviseResult {
205            new_budget,
206            new_task_budget,
207            new_links_budget,
208        }
209    }
210
211    /// 模拟`BudgetFunctions.update`
212    ///
213    /// # 📄OpenNARS
214    ///
215    /// Update a belief
216    ///
217    /// @param task   The task containing new belief
218    /// @param bTruth Truth value of the previous belief
219    /// @return Budget value of the updating task
220    fn update(
221        task_truth: &impl Truth,
222        task_budget: &mut Self,
223        b_truth: &impl Truth,
224    ) -> BudgetValue {
225        /* 📄OpenNARS源码:
226        Truth tTruth = task.getSentence().getTruth();
227        float dif = tTruth.getExpDifAbs(bTruth);
228        float priority = or(dif, task.getPriority());
229        float durability = aveAri(dif, task.getDurability());
230        float quality = truthToQuality(bTruth);
231        return new BudgetValue(priority, durability, quality); */
232        // * 🚩计算落差
233        let dif = ShortFloat::from_float(task_truth.expectation_abs_dif(b_truth));
234        // * 🚩根据落差计算预算值
235        // * 📝优先级 = 落差 | 任务
236        // * 📝耐久度 = (落差 + 任务) / 2
237        // * 📝质量 = 信念真值→质量
238        let priority = dif | task_budget.priority();
239        let durability = ShortFloat::arithmetical_average([dif, task_budget.durability()]);
240        let quality = Self::truth_to_quality(task_truth);
241        BudgetValue::new(priority, durability, quality)
242    }
243
244    /* ----------------------- Links ----------------------- */
245
246    /// 模拟`BudgetFunctions.distributeAmongLinks`
247    ///
248    /// # 📄OpenNARS
249    /// Distribute the budget of a task among the links to it
250    ///
251    /// @param b The original budget
252    /// @param n Number of links
253    /// @return Budget value for each link
254    fn distribute_among_links(&self, n: usize) -> BudgetValue {
255        /* 📄OpenNARS源码:
256        float priority = (float) (b.getPriority() / Math.sqrt(n));
257        return new BudgetValue(priority, b.getDurability(), b.getQuality()); */
258        // * 📝优先级 = 原 / √链接数
259        // * 📝耐久度 = 原
260        // * 📝质量 = 原
261        let priority = self.priority().to_float() / (n as Float).sqrt();
262        BudgetValue::new(
263            ShortFloat::from_float(priority),
264            self.durability(),
265            self.quality(),
266        )
267    }
268
269    /* ----------------------- Concept ----------------------- */
270
271    /// 模拟`BudgetFunctions.activate`
272    /// * 🚩【2024-05-02 20:55:40】虽然涉及「概念」,但实际上只用到了「概念作为预算值的部分」
273    /// * 📌【2024-05-02 20:56:11】目前要求「概念」一方使用同样的「短浮点」
274    /// * 🚩【2024-05-03 14:58:03】此处是「修改」语义
275    /// * ⚠️参数顺序和OpenNARS仍然保持相同:`self`指代其中的`concept`参数
276    ///
277    /// # 📄OpenNARS
278    ///
279    /// Activate a concept by an incoming TaskLink
280    ///
281    /// @param concept The concept
282    /// @param budget  The budget for the new item
283    #[doc(alias = "activate")]
284    fn activate_to_concept(&self, concept: &Concept) -> BudgetValue {
285        // * 🚩直接计算
286        let [cp, cd] = [concept.priority(), concept.durability()];
287        let [bp, bd] = [self.priority(), self.durability()];
288        // * 📝优先级 = 概念 | 参考
289        // * 📝耐久度 = (概念 + 参考) / 2
290        // * 📝质量 = 综合所有词项链后的新「质量」
291        BudgetValue::new(
292            cp | bp,
293            ShortFloat::arithmetical_average([cd, bd]),
294            Self::concept_total_quality(concept),
295        )
296    }
297
298    /* ---------------- Bag functions, on all Items ------------------- */
299
300    /// 模拟`BudgetFunctions.forget`
301    /// * 🚩【2024-05-03 14:57:06】此处是「修改」语义,而非「创建新值」语义
302    /// * 🚩【2024-06-24 16:13:41】现在跟从改版OpenNARS,转为「创建新值」语义
303    ///
304    /// # 📄OpenNARS
305    ///
306    /// Decrease Priority after an item is used, called in Bag
307    ///
308    /// After a constant time, p should become d*p.
309    ///
310    /// Since in this period, the item is accessed c*p times, each time p-q should multiple d^(1/(c*p)).
311    ///
312    /// The intuitive meaning of the parameter "forgetRate" is:
313    /// after this number of times of access, priority 1 will become d, it is a system parameter adjustable in run time.
314    ///
315    /// - @param budget            The previous budget value
316    /// - @param forgetRate        The budget for the new item
317    /// - @param relativeThreshold The relative threshold of the bag
318    fn forget(&self, forget_rate: Float, relative_threshold: Float) -> Float {
319        /* 📄OpenNARS源码:
320        double quality = budget.getQuality() * relativeThreshold; // re-scaled quality
321        double p = budget.getPriority() - quality; // priority above quality
322        if (p > 0) {
323            quality += p * Math.pow(budget.getDurability(), 1.0 / (forgetRate * p));
324        } // priority Durability
325        budget.setPriority((float) quality); */
326        let [p, d, q] = self.pdq_float();
327        // * 🚩先放缩「质量」
328        let scaled_q = q * relative_threshold;
329        // * 🚩计算优先级和「放缩后质量」的差
330        let dif_p_q = p - scaled_q;
331        // * 🚩计算新的优先级
332        match dif_p_q > 0.0 {
333            // * 🚩差值 > 0 | 衰减
334            true => scaled_q + dif_p_q * d.powf(1.0 / (forget_rate * dif_p_q)),
335            // * 🚩差值 < 0 | 恒定
336            false => scaled_q,
337        }
338    }
339
340    /// 模拟`BudgetValue.merge`,亦与`BudgetFunctions.merge`相同
341    /// * 📝【2024-05-03 14:55:29】虽然现在「预算函数」以「直接创建新值」为主范式,
342    ///   * 但在用到该函数的`merge`方法上,仍然是「修改」语义——需要可变引用
343    /// * 🚩【2024-06-24 16:15:22】现在跟从改版OpenNARS,直接创建新值
344    ///
345    /// # 📄OpenNARS
346    ///
347    /// ## `BudgetValue`
348    ///
349    /// Merge one BudgetValue into another
350    ///
351    /// ## `BudgetFunctions`
352    ///
353    /// Merge an item into another one in a bag, when the two are identical
354    /// except in budget values
355    ///
356    /// @param baseValue   The budget value to be modified
357    /// @param adjustValue The budget doing the adjusting
358    fn merge(&self, other: &impl Budget) -> BudgetValue {
359        let p = self.priority().max(other.priority());
360        let d = self.durability().max(other.durability());
361        let q = self.quality().max(other.quality());
362        BudgetValue::new(p, d, q)
363    }
364
365    /// Forward inference result and adjustment
366    fn forward(truth: Option<&impl Truth>, content: Option<&Term>) -> BudgetInferenceParameters {
367        // * 📝真值转质量,用不到词项
368        debug_assert_matches!((truth, content), (Some(..), None));
369        let inference_quality = truth.map_or(ShortFloat::ONE, Self::truth_to_quality);
370        let complexity = 1;
371        BudgetInferenceParameters {
372            inference_quality, // 默认值:1
373            complexity,
374        }
375    }
376
377    /// Backward inference result and adjustment, stronger case
378    fn backward(truth: Option<&impl Truth>, content: Option<&Term>) -> BudgetInferenceParameters {
379        // * 📝真值转质量,用不到词项
380        debug_assert_matches!((truth, content), (Some(..), None));
381        let inference_quality = truth.map_or(ShortFloat::ONE, Self::truth_to_quality);
382        let complexity = 1;
383        BudgetInferenceParameters {
384            inference_quality, // 默认值:1
385            complexity,
386        }
387    }
388
389    /// Backward inference result and adjustment, weaker case
390    fn backward_weak(
391        truth: Option<&impl Truth>,
392        content: Option<&Term>,
393    ) -> BudgetInferenceParameters {
394        // * 📝真值转质量,用不到词项
395        debug_assert_matches!((truth, content), (Some(..), None));
396        let inference_quality =
397            ShortFloat::W2C1() * truth.map_or(ShortFloat::ONE, Self::truth_to_quality);
398        let complexity = 1;
399        BudgetInferenceParameters {
400            inference_quality, // 默认值:1
401            complexity,
402        }
403    }
404
405    /// Forward inference with CompoundTerm conclusion
406    fn compound_forward(
407        truth: Option<&impl Truth>,
408        content: Option<&Term>,
409    ) -> BudgetInferenceParameters {
410        // * 📝真值转质量,用到词项的复杂度
411        debug_assert_matches!((truth, content), (Some(..), Some(..)));
412        let inference_quality = truth.map_or(ShortFloat::ONE, Self::truth_to_quality);
413        let complexity = content.map_or(1, Term::complexity);
414        BudgetInferenceParameters {
415            inference_quality, // 默认值:1
416            complexity,        // 默认值:1
417        }
418    }
419
420    /// Backward inference with CompoundTerm conclusion, stronger case
421    fn compound_backward(
422        truth: Option<&impl Truth>,
423        content: Option<&Term>,
424    ) -> BudgetInferenceParameters {
425        // * 📝用到词项的复杂度,用不到真值
426        debug_assert_matches!((truth, content), (None, Some(..)));
427        let inference_quality = ShortFloat::ONE;
428        let complexity = content.map_or(1, Term::complexity);
429        BudgetInferenceParameters {
430            inference_quality,
431            complexity, // 默认值:1
432        }
433    }
434
435    /// Backward inference with CompoundTerm conclusion, weaker case
436    fn compound_backward_weak(
437        truth: Option<&impl Truth>,
438        content: Option<&Term>,
439    ) -> BudgetInferenceParameters {
440        // * 📝用到词项的复杂度,用不到真值
441        debug_assert_matches!((truth, content), (None, Some(..)));
442        let inference_quality = ShortFloat::W2C1();
443        let complexity = content.map_or(1, Term::complexity);
444        BudgetInferenceParameters {
445            inference_quality,
446            complexity, // 默认值:1
447        }
448    }
449
450    /// 从「预算推理函数 枚举」到「预算推理函数指针」
451    fn budget_inference_function_from<T: Truth>(
452        function_enum: BudgetInferenceFunction,
453    ) -> BudgetInferenceF<T> {
454        use BudgetInferenceFunction::*;
455        match function_enum {
456            Forward => Self::forward,
457            Backward => Self::backward,
458            BackwardWeak => Self::backward_weak,
459            CompoundForward => Self::compound_forward,
460            CompoundBackward => Self::compound_backward,
461            CompoundBackwardWeak => Self::compound_backward_weak,
462        }
463    }
464    /// Common processing for all inference step
465    ///
466    /// @param inferenceQuality [] Quality of the inference
467    /// @param complexity       [] Syntactic complexity of the conclusion
468    /// @return [] Budget of the conclusion task
469    fn budget_inference<T: Truth>(
470        function: BudgetInferenceFunction,
471        truth: Option<&T>,
472        content: Option<&Term>,
473        task_link_budget: &impl Budget,
474        belief_link_budget: Option<&impl Budget>,
475        target_activation: ShortFloat,
476    ) -> BudgetInferenceResult {
477        // * 🚩应用函数,提取其中的「推理优先级」和「复杂度」
478        let budget_inference_function = Self::budget_inference_function_from::<T>(function);
479        let BudgetInferenceParameters {
480            inference_quality,
481            complexity,
482        } = budget_inference_function(truth, content);
483        // * 🚩获取「任务链」和「信念链」的优先级(默认0)与耐久度(默认1)
484        // * 📝p = self ?? 0
485        // * 📝d = self ?? 1
486        let [t_link_p, t_link_d] = [task_link_budget.priority(), task_link_budget.durability()];
487        let [b_link_p, b_link_d] = match belief_link_budget {
488            // * 🚩有信念链⇒取其值
489            Some(budget) => [budget.priority(), budget.durability()],
490            // * 🚩无信念链⇒默认为[0, 1]
491            None => [ShortFloat::ZERO, ShortFloat::ONE],
492        };
493        // * 🚩更新预算
494        // * 📝p = task | belief
495        // * 📝d = (task / complexity) & belief
496        // * 📝q = inferenceQuality / complexity
497        let [p, d, q] = [
498            t_link_p | b_link_p,
499            (t_link_d / complexity) & b_link_d,
500            inference_quality / complexity,
501        ];
502        // * 🚩有信念链⇒更新信念链预算值
503        // * 🚩【2024-06-20 17:11:30】现在返回一个新的预算值
504        let new_belief_link_budget = belief_link_budget.map(|b_link_budget| {
505            // * 📌此处仅在「概念推理」中出现:能使用可空值处理
506            // * 📝p = belief | quality | targetActivation
507            // * 📝d = belief | quality
508            // * 📝q = belief
509            // * 🚩提升优先级
510            let [b_link_p, b_link_d, b_link_q] = b_link_budget.pdq();
511            BudgetValue::new(b_link_p | q | target_activation, b_link_d | q, b_link_q)
512        });
513        // * 🚩返回预算值
514        BudgetInferenceResult {
515            new_budget: BudgetValue::new(p, d, q),
516            new_belief_link_budget,
517        }
518    }
519}
520
521/// 修正规则的预算推理结果
522/// * 🎯用于[`BudgetFunctions::revise`]
523pub struct ReviseResult {
524    /// 新预算
525    pub new_budget: BudgetValue,
526    /// 新任务预算
527    pub new_task_budget: BudgetValue,
528    /// [新任务链预算, 新信念链预算](可空)
529    /// * 📌左边任务链,右边信念链
530    /// * 🎯统一二者的可空性 from `feedbackToLinks`
531    pub new_links_budget: Option<[BudgetValue; 2]>,
532}
533
534mod budget_inference_functions {
535    use super::*;
536
537    pub struct BudgetInferenceParameters {
538        /// * 🚩目前只用于「预算推理」的被除数(除以复杂度)上
539        pub inference_quality: ShortFloat,
540        pub complexity: usize,
541    }
542
543    /// 统一的「预算值参数计算函数」指针类型(带泛型)
544    pub type BudgetInferenceF<T> = fn(Option<&T>, Option<&Term>) -> BudgetInferenceParameters;
545
546    /// 所有可用的预算值函数
547    /// * 🎯统一呈现「在推理过程中计算预算值」的「预算超参数」
548    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
549    pub enum BudgetInferenceFunction {
550        /// 前向推理
551        Forward,
552        /// 反向强推理
553        Backward,
554        /// 反向弱推理
555        BackwardWeak,
556        /// 复合前向推理
557        CompoundForward,
558        /// 复合反向强推理
559        CompoundBackward,
560        /// 复合反向弱推理
561        CompoundBackwardWeak,
562    }
563
564    pub struct BudgetInferenceResult {
565        /// 预算推理算出的新预算
566        pub new_budget: BudgetValue,
567        /// 预算推理算出的「新信念链预算」
568        pub new_belief_link_budget: Option<BudgetValue>,
569    }
570}
571pub use budget_inference_functions::*;
572
573/// 自动实现「预算函数」
574/// * 🎯直接在「预算值」上加功能
575impl<B: Budget> BudgetFunctions for B {}
576
577/// TODO: 单元测试
578#[cfg(test)]
579mod tests {}