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 {}