dsh-math-calculator 2.2.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,156 @@
1
+ /**
2
+ * 公式参数的**量纲校验**:把「位移 m」这类描述里的单位当成真单位来用。
3
+ *
4
+ * ## 为什么这一步值得做
5
+ *
6
+ * 公式库 442 条,每个参数的描述里其实**已经写着单位**:
7
+ *
8
+ * ```
9
+ * velocity_uniform: s → "位移 m" t → "时间 s"
10
+ * kinetic_energy: m → "质量 kg" v → "速度 m/s"
11
+ * ```
12
+ *
13
+ * 这些单位以前只用于**显示**,不参与任何判断。于是:
14
+ *
15
+ * ```
16
+ * {formula:"velocity_uniform", overrides:{s:10, t:1}}
17
+ * → 10 m/s ← 数字对了,但没人能确认 s 给的是米而不是公里
18
+ * ```
19
+ *
20
+ * 现在让调用方可以**带单位给参数**,工具负责校验与换算:
21
+ *
22
+ * ```
23
+ * overrides:{s:"10 m", t:"1 s"} → 10 m/s ✓
24
+ * overrides:{s:"10 km", t:"1 h"} → 2.7778 m/s ✓ 自动换算到 SI
25
+ * overrides:{s:"10 kg", t:"1 s"} → 量纲不符:s 是长度,kg 是质量 ✗
26
+ * ```
27
+ *
28
+ * ## 一个必须守住的边界:**只校验,不猜**
29
+ *
30
+ * 裸数字照旧原样接受 —— 不假装能知道 `10` 是米还是公里。
31
+ * 单位只在调用方**主动写了**的时候才生效。这跟本项目一贯的取向一致:
32
+ * 把判据交给知道答案的一方,工具只负责比对与换算。
33
+ *
34
+ * @module dsh-math-calculator/param-units
35
+ */
36
+ /**
37
+ * 从参数描述里抠出单位符号:取描述里**最后一个像单位的片段**。
38
+ *
39
+ * 描述写得很自由("位移 m"、"时间 s"、"质量1 kg"、"半径 r / m"),
40
+ * 所以判据定为:末尾那个不含中文、不含空格的短片段,且必须是已知单位。
41
+ *
42
+ * 为什么不用正则硬猜:`"质量1 kg"` 里的 `1` 与 `m3`、`s^-1` 这些形态都会把
43
+ * 朴素正则带偏。先用「已知单位表」兜底,比先写好正则再补漏洞更稳。
44
+ *
45
+ * @param desc - 参数描述
46
+ * @param isKnownUnit - 判断某字符串是否为已知单位(由调用方注入,避免模块耦合)
47
+ * @returns 单位名;抠不出时返回空串
48
+ */
49
+ export declare function unitFromDescription(desc: string, isKnownUnit: (name: string) => boolean): string;
50
+ /** 一个参数的解析结果。 */
51
+ export interface ParsedParamValue {
52
+ /** 换算到该参数声明单位的数值。 */
53
+ value: number;
54
+ /** 调用方写的原始单位(没写时为空)。 */
55
+ givenUnit?: string;
56
+ /** 实际做了一次换算(givenUnit 不是声明单位时)。 */
57
+ converted?: boolean;
58
+ }
59
+ /**
60
+ * 解析一个「带单位」参数值。
61
+ *
62
+ * @param raw - 调用方传的值(`"10 m"`、`"10 km"`、或裸数字)
63
+ * @param declaredUnit - 参数描述里声明的单位(如 `m`);为空表示该参数没有单位约定
64
+ * @param convertUnit - 换算函数(由调用方注入,签名对齐 `units.ts` 的 convert)
65
+ * @returns 解析结果;失败时返回 `{ error, hint }`
66
+ */
67
+ export declare function parseParamValue(raw: unknown, declaredUnit: string, convertUnit: (value: number, from: string, to: string) => {
68
+ ok: boolean;
69
+ value?: number;
70
+ error?: string;
71
+ hint?: string;
72
+ }): ParsedParamValue | {
73
+ error: string;
74
+ hint: string;
75
+ };
76
+ /**
77
+ * 中文传统单位族:这些单位一旦出现在输入里,输出就该照顾同一体系。
78
+ *
79
+ * ## 为什么需要它(用户 2026-11-07 直接要求)
80
+ *
81
+ * 用户原话:「最好使用同一体系单位,把 60 km 折算成 里。」
82
+ *
83
+ * ```
84
+ * 输入:V='30 里/h', T='2 时辰' → 60 km
85
+ * 用户要的:60 km ≈ 60 里(因为 60 km ÷ 1 m 尺度…)
86
+ * ```
87
+ *
88
+ * 这不是计算问题 —— 数值本来就对。问题是**输出换了体系**:
89
+ * 用里和时辰下的订单,收据开了公里和小时。作者写正文时得自己再换一遍,
90
+ * 而每换一次就多一次出错机会(这正是本项目一直在对付的那类错误)。
91
+ *
92
+ * ## 收哪些族
93
+ *
94
+ * 只收**有稳定换算系数**的传统单位族。不收「一箭之地」「一盏茶工夫」
95
+ * 这类需要上下文的 —— 见 units-data 的收录原则。
96
+ */
97
+ export declare const TRADITIONAL_FAMILIES: Record<string, string[]>;
98
+ /**
99
+ * 判断一批输入的参数用了哪些传统单位族。
100
+ *
101
+ * @param units - 输入里出现过的单位名(如 `['里', '时辰']`)
102
+ * @returns 命中的量纲键(如 `['length', 'time']`)
103
+ */
104
+ export declare function familiesOfUnits(units: string[]): string[];
105
+ /**
106
+ * 把 SI 结果**按输入的传统单位族折算一遍**,作为附注。
107
+ *
108
+ * ## 用户原话
109
+ *
110
+ * 「最好使用同一体系单位,把 60 km 折算成 里。」
111
+ *
112
+ * ## 为什么做成附注而不是替换主结果
113
+ *
114
+ * 主结果必须保持 SI —— 那是公式的参数约定,也是与其它计算对接的口径。
115
+ * 而作者写正文时用的是里/丈/时辰,所以**两种都要给**:
116
+ *
117
+ * ```
118
+ * 结果: 2.08333333 m/s
119
+ * 【同体系折算】约 2.25 里/时辰(你的输入用了 里、时辰)
120
+ * ```
121
+ *
122
+ * 替换掉主结果会破坏「公式内部一律 SI」这条约定;只给 SI 又要作者自己换算,
123
+ * 而每换一次就多一次出错机会。附注是唯一不牺牲任何一边的做法。
124
+ *
125
+ * @param siText - 已格式化的 SI 结果文本(如 `2.08333333 m/s`)
126
+ * @param families - 输入里用到的传统单位族(见 `familiesOfUnits`)
127
+ * @param convert - 换算函数(注入以避免模块耦合)
128
+ * @returns 折算附注行;不需要时为 null
129
+ */
130
+ export declare function traditionalNote(siText: string, families: string[], convert: (value: number, from: string, to: string) => {
131
+ ok: boolean;
132
+ value?: number;
133
+ }): string | null;
134
+ /**
135
+ * 默认换算适配器:直接接 `units.ts` 的 `convert`。
136
+ *
137
+ * 为什么放在这里而不是让每个调用方自己写一遍:
138
+ * `convert` 返回的三层结构(`{ok, conversion:{result}}`)很容易读错字段 ——
139
+ * 本项目已经踩过一次(读 `conversion.value` 拿到 undefined)。
140
+ * 集中一处,读错了只改一个地方。
141
+ */
142
+ export declare function makeConverter(convert: (value: number, from: string, to: string) => {
143
+ ok: true;
144
+ conversion: {
145
+ result: number;
146
+ };
147
+ } | {
148
+ ok: false;
149
+ error: string;
150
+ hint: string;
151
+ }): (value: number, from: string, to: string) => {
152
+ ok: boolean;
153
+ value?: number;
154
+ error?: string;
155
+ hint?: string;
156
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-math-calculator",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
4
4
  "private": false,
5
5
  "description": "DSH 原生的高精度数学计算器插件:BigNumber 64 位求值、符号微积分、线性代数(秩/零空间/奇异值/特征值)、统计概率与回归、微分方程、单位换算、395 条公式库(三轴标签)、物理常数推演。全部本地执行,不联网。附带一组可选的世界观推演工具。",
6
6
  "type": "module",
package/src/format.ts CHANGED
@@ -182,13 +182,61 @@ export function formatValue(
182
182
  * 的纯数字路径(那里有完整的小数位与丢精度判据),单位原样拼回。
183
183
  * 这样既不重复实现截断逻辑,也不会改变整数完整这条不变量。
184
184
  */
185
+ /**
186
+ * ★ 拆不开时必须**带着单位**退回,不能落进下面的纯数字分支。
187
+ *
188
+ * 踩过:`1 L/minute to m^3/s` 的格式化结果是 `1.6666e-5 m^3 / s`,
189
+ * 而 `trimUnitNumber` 的正则只认「数字 + 一个单位符号」,遇复合单位返回空串。
190
+ * 于是控制流落进末尾的通用分支,那里对 Unit 值走 `numeric === null`,
191
+ * 输出只剩 `1.666666666666666666666666666666666666666`——**单位没了**。
192
+ *
193
+ * 那个数字本身是对的(1.67e-5),但少了 `m^3 / s` 之后,读者会把它当成
194
+ * 「1.67」,而真实值是 0.0000167,差 10 万倍。
195
+ *
196
+ * 所以这里拆不开就直接用 mathjs 的原格式(单位一定在里面)。
197
+ */
185
198
  try {
186
- const rendered = trimUnitNumber(
187
- math, math.format(value, { notation: 'auto', precision }), precision, displayPrecision,
188
- )
199
+ /**
200
+ * ## 复合单位必须**自己拼单位**:mathjs 的 toString 会丢它
201
+ *
202
+ * 实测 `1 L/minute to m^3/s`(一个 `to` 到复合单位的值):
203
+ *
204
+ * ```
205
+ * math.format(v, {notation:'auto', precision}) → "1.6666666666666…" ← 没有单位
206
+ * String(v) → "1.6666666666666…" ← 也没有
207
+ * v.formatUnits() → "m^3 / s" ← 单位在这里
208
+ * v.value → 0.000016666… ← 基准值
209
+ * ```
210
+ *
211
+ * 根因:`Unit#toString` 会先 `simplify()`,而简化对 `m^3 / s` 这类
212
+ * **多单位复合**处理不了,它就只输出数值那一半。
213
+ * 单单位(`kW`、`cm`)不受影响,所以只有复合单位丢 ——
214
+ * 这也解释了为什么 `1 MJ/day to kW` 一直是对的。
215
+ *
216
+ * 后果很严重:`1.67e-5` 少了 `m^3 / s` 之后看着像「1.67」,
217
+ * 而真实值是 0.0000167,**差 10 万倍**,数字本身没有任何异常。
218
+ *
219
+ * 所以这里显式拆成「数值 + formatUnits()」两段自己拼。
220
+ */
221
+ const unitText = typeof value.formatUnits === 'function' ? value.formatUnits() : ''
222
+ const numericText = math.format(value, { notation: 'auto', precision })
223
+ /**
224
+ * ★ 只在**确实缺单位**时才补。
225
+ *
226
+ * `math.format` 的行为不一致:单单位(`kW`、`cm`)本来就会带上,
227
+ * 多单位复合(`m^3 / s`)才丢。一律追加会得到 `200 cm cm`。
228
+ *
229
+ * 判据用「结果里有没有出现单位的首个符号」—— 够简单,且不会误判:
230
+ * 数值部分只有数字、指数、符号,不会包含单位名。
231
+ */
232
+ const unitHead = unitText.split(/[\s/]/)[0]
233
+ const hasUnit = unitHead !== '' && numericText.includes(unitHead)
234
+ const raw = unitText === '' || hasUnit ? numericText : numericText + ' ' + unitText
235
+ const rendered = trimUnitNumber(math, raw, precision, displayPrecision)
189
236
  if (rendered !== '') return rendered
237
+ return raw
190
238
  } catch {
191
- /* 拆不开就退回原格式 */
239
+ /* 拼不出来:交给下面的兜底 */
192
240
  }
193
241
  }
194
242
  }
@@ -0,0 +1,220 @@
1
+ /**
2
+ * 公式库第十批:**生物与宏观经济**的运行式子。
3
+ *
4
+ * ## 为什么这两块一起补
5
+ *
6
+ * 生物类原有 26 条,但偏向**医学剂量**(体重给药、输液速率、血液酒精);
7
+ * 经济类原有 26 条,几乎全是**金融**(复利、年金、NPV、CAGR)。
8
+ *
9
+ * 而写世界观真正会问的是另两类问题:
10
+ *
11
+ * ```
12
+ * 生物:这片草原能养多少头牛?这窝猪多久出栏?这个生态能支撑多少顶级捕食者?
13
+ * 经济:这个国家一年产多少?劳动力有多少?投资能带来多少增长?钱发多了会怎样?
14
+ * ```
15
+ *
16
+ * 这一批补的就是这些**运行层面**的式子,而不是又一批财务指标。
17
+ *
18
+ * ## 收录判据
19
+ *
20
+ * 同既有几批:闭合式、带量纲、有出处、不重复。经验系数标出取值范围。
21
+ *
22
+ * @module dsh-math-calculator/formula-library-bioecon
23
+ */
24
+
25
+ import type { FormulaDef } from './formula-library.js'
26
+
27
+ const p = (value: number, desc: string) => ({ value, desc })
28
+ const req = (desc: string) => ({ value: null, desc })
29
+
30
+ /** 生物与宏观 11 条:生态承载、畜禽养殖、人口劳动力、货币与通胀。 */
31
+ export const BIOECON_FORMULAS: FormulaDef[] = [
32
+ // ══════════════════════════════════════════════════════════════════
33
+ // A. 生态与畜牧(土地能养多少、多久出栏、能撑几级食物链)
34
+ // ══════════════════════════════════════════════════════════════════
35
+ {
36
+ id: 'grazing_capacity',
37
+ cat: 'biology/ecology',
38
+ name: '草地载畜量',
39
+ expr: 'area * yield_per_ha * util / (intake * days)',
40
+ params: {
41
+ area: req('草场面积 ha(公顷,1 ha = 1e4 m²)'),
42
+ yield_per_ha: req('单位面积产草量 kg/(ha·年)(温带草原 2000~5000,荒漠 200~500)'),
43
+ util: p(0.5, '可利用率(放牧通常 0.4~0.6,留一半给草场恢复)'),
44
+ intake: req('每头每日采食量 kg/头·日(牛 40~60,羊 5~8)'),
45
+ days: p(365, '放牧天数(全年 365,季节性牧场按实际)'),
46
+ },
47
+ unit: '头(可承载标准畜单位)',
48
+ desc: '载畜量 = 面积 × 产草量 × 利用率 /(采食量 × 天数)。**「留一半」不是保守,是必需**:草地超载后会退化,第二年产量直接腰斩。这条是游牧与畜牧设定的地基。',
49
+ keys: ['载畜量', '草地', '放牧', 'grazing', '畜牧', '草原'],
50
+ frequency: 'high',
51
+ layer: 'phenomenological',
52
+ domains: ['biology', 'perception'],
53
+ },
54
+ {
55
+ id: 'livestock_growth_time',
56
+ cat: 'biology/ecology',
57
+ name: '畜禽出栏时间(按日增重)',
58
+ expr: '(target_weight - birth_weight) / adg',
59
+ params: {
60
+ target_weight: req('出栏体重 kg'),
61
+ birth_weight: req('初生体重 kg(仔猪 1.5,犊牛 40,羔羊 3)'),
62
+ adg: req('平均日增重 kg/日(肉猪 0.7~1.0,肉牛 0.8~1.2,肉鸡 0.05)'),
63
+ },
64
+ unit: '日',
65
+ desc: '出栏天数 = (出栏重 − 初生重)/日增重。**猪约 180 天、牛约 450 天、肉鸡约 42 天**——这个比值决定了肉价与牧场的周转率。日增重随饲料与品种变,写设定时要说清是哪种。',
66
+ keys: ['出栏', '日增重', 'livestock', '养殖', '肉'],
67
+ frequency: 'high',
68
+ layer: 'phenomenological',
69
+ domains: ['biology', 'perception'],
70
+ },
71
+ {
72
+ id: 'feed_conversion_ratio',
73
+ cat: 'biology/ecology',
74
+ name: '饲料转化率(料肉比)',
75
+ expr: 'feed_consumed / weight_gain',
76
+ params: {
77
+ feed_consumed: req('消耗饲料总量 kg(干物质)'),
78
+ weight_gain: req('增重 kg'),
79
+ },
80
+ unit: '无量纲(kg 料/kg 肉)',
81
+ desc: '料肉比 = 耗料/增重。**牛肉 6~8、猪肉 2.5~3.5、肉鸡 1.6~2.0**——这个数字直接决定「养一头牛要多少亩地」。牛高是因为反刍动物维持消耗大,不是品种差。',
82
+ keys: ['料肉比', '饲料转化', 'feed conversion', 'FCR'],
83
+ frequency: 'high',
84
+ layer: 'phenomenological',
85
+ domains: ['biology', 'perception'],
86
+ },
87
+ {
88
+ id: 'crop_yield_npp',
89
+ cat: 'biology/ecology',
90
+ name: '农田可收获能量(由 NPP 与收获指数)',
91
+ expr: 'NPP * area * hi * 1e4',
92
+ params: {
93
+ NPP: req('净初级生产力 g/(m²·年)(温带农田 800~1500,热带 2000+)'),
94
+ area: req('面积 ha'),
95
+ hi: p(0.4, '收获指数(谷物 0.4~0.5,块根作物 0.6,牧草 0.9)'),
96
+ },
97
+ unit: 'g(干物质)',
98
+ desc: '可收获干物质 = NPP × 面积 × 收获指数。收获指数是「长出来的东西里有多少是我们要吃的部分」——谷物的 0.4 意味着**六成生物量是秸秆**,那正是牲畜饲料与燃料的来源。',
99
+ keys: ['NPP', '收获指数', '产量', '作物', 'yield'],
100
+ frequency: 'high',
101
+ layer: 'phenomenological',
102
+ domains: ['biology', 'perception'],
103
+ },
104
+ {
105
+ id: 'trophic_chain_support',
106
+ cat: 'biology/ecology',
107
+ name: '营养级可支撑的生物量(十分之一定律)',
108
+ expr: 'primary * eff^level',
109
+ params: {
110
+ primary: req('初级生产者生物量 kg'),
111
+ eff: p(0.1, '营养级传递效率(陆地 0.1,海洋 0.1~0.15,以肉食为主时更低)'),
112
+ level: req('营养级层数(草食 1,一级肉食 2,顶级 3~4)'),
113
+ },
114
+ unit: 'kg',
115
+ desc: '每升一级只剩约 10%。**这就是为什么食物链一般不超过 5 级,也是为什么顶级捕食者稀少**:要养活 1 kg 狼,需要 10 kg 兔、100 kg 草。写生态设定时这条能立刻否掉「满地猛兽」的方案。',
116
+ keys: ['营养级', '食物链', '十分之一', 'trophic', '捕食者'],
117
+ frequency: 'high',
118
+ layer: 'derived',
119
+ domains: ['biology'],
120
+ },
121
+ // ══════════════════════════════════════════════════════════════════
122
+ // B. 人口、劳动力与产出(宏观经济的运行层)
123
+ // ══════════════════════════════════════════════════════════════════
124
+ {
125
+ id: 'labor_force',
126
+ cat: 'econ/macro',
127
+ name: '劳动力人口(由总人口与年龄结构)',
128
+ expr: 'population * participation',
129
+ params: {
130
+ population: req('总人口'),
131
+ participation: p(0.5, '劳动参与率(农业社会 0.45~0.55,工业化 0.55~0.65)'),
132
+ },
133
+ unit: '人',
134
+ desc: '劳力 = 总人口 × 参与率。**农业社会的 0.45 里含着大量童工与老人**——这个比值决定了「能征多少兵」与「能修多大工程」的上限。',
135
+ keys: ['劳动力', '人口', 'labor', '劳力'],
136
+ frequency: 'high',
137
+ layer: 'conventional',
138
+ domains: ['economics', 'society'],
139
+ },
140
+ {
141
+ id: 'gdp_from_labor',
142
+ cat: 'econ/macro',
143
+ name: '总产出(人力 × 人均产出)',
144
+ expr: 'labor * output_per_worker',
145
+ params: {
146
+ labor: req('劳动力人数'),
147
+ output_per_worker: req('人均年产出(按货币或实物统一定义)'),
148
+ },
149
+ unit: '与人均产出同单位(通常为年产值)',
150
+ desc: '产出 = 劳力 × 人均产出。最简单的宏观核算模型:**想提高总产出只有两条路——加人或提高单产**。人均产值翻倍比人口翻倍难得多,所以长期增长几乎全靠后者。',
151
+ keys: ['总产出', 'GDP', '宏观', '产值'],
152
+ frequency: 'high',
153
+ layer: 'conventional',
154
+ domains: ['economics'],
155
+ },
156
+ {
157
+ id: 'macro_savings_rate',
158
+ cat: 'econ/macro',
159
+ name: '储蓄率与投资能力',
160
+ expr: 'output * savings_rate',
161
+ params: {
162
+ output: req('总产出'),
163
+ savings_rate: p(0.2, '储蓄率(农业社会 0.05~0.15,高速增长期 0.25~0.40)'),
164
+ },
165
+ unit: '与总产出同单位',
166
+ desc: '可投资额 = 产出 × 储蓄率。**这是增长的燃料**:高速增长的经济体储蓄率往往很高(东亚 0.3+),而贫困农业社会几乎全被消费掉,攒不出投资。',
167
+ keys: ['储蓄', '投资', 'savings', '资本积累'],
168
+ frequency: 'high',
169
+ layer: 'conventional',
170
+ domains: ['economics'],
171
+ },
172
+ {
173
+ id: 'capital_output_ratio',
174
+ cat: 'econ/macro',
175
+ name: '资本产出比(增长的资本需求)',
176
+ expr: 'savings_rate / growth_rate',
177
+ params: {
178
+ savings_rate: req('储蓄率'),
179
+ growth_rate: req('目标增长率(小数)'),
180
+ },
181
+ unit: '无量纲(资本/产出)',
182
+ desc: '哈罗德-多马式的推导:所需资本产出比 = 储蓄率 / 增长率。**想每年增长 5% 而储蓄率只有 10%,需要资本产出比 2(即每 1 元产出要 2 元资本)**——如果实际要 4,那 5% 就达不到。这条能立刻判断一个增长目标现不现实。',
183
+ keys: ['资本产出比', '哈罗德', '增长', '投入产出'],
184
+ frequency: 'medium',
185
+ layer: 'derived',
186
+ domains: ['economics'],
187
+ },
188
+ {
189
+ id: 'money_supply_from_base',
190
+ cat: 'econ/macro',
191
+ name: '货币供应量(基础货币 × 货币乘数)',
192
+ expr: 'base_money * multiplier',
193
+ params: {
194
+ base_money: req('基础货币(流通现金 + 准备金)'),
195
+ multiplier: p(5, '货币乘数(现代银行 8~12,准备金率高时 2~4,金属货币制接近 1)'),
196
+ },
197
+ unit: '与基础货币同单位',
198
+ desc: '广义货币 = 基础货币 × 乘数。**金属货币制的乘数接近 1,现代部分准备金制能放大 10 倍**——这解释了为什么古代通货膨胀罕见而现代可以剧烈。架空设定里「银行体系发达程度」直接改这个乘数。',
199
+ keys: ['货币供应', '乘数', 'money supply', '通胀', '银行'],
200
+ frequency: 'high',
201
+ layer: 'derived',
202
+ domains: ['economics'],
203
+ },
204
+ {
205
+ id: 'inflation_from_money_growth',
206
+ cat: 'econ/macro',
207
+ name: '货币增长与产出增长之差(通胀压力)',
208
+ expr: '(money_growth - output_growth) * 100',
209
+ params: {
210
+ money_growth: req('货币供应年增长率(小数,如 0.15)'),
211
+ output_growth: req('实际产出年增长率(小数,如 0.03)'),
212
+ },
213
+ unit: '%(年通胀率近似)',
214
+ desc: '通胀 ≈ 货币增速 − 产出增速。**这是最实用的通胀判据**:钱印得比东西增长快多少,物价就涨多少。0.15 对 0.03 意味着约 12% 的年通胀——三年物价翻倍。写「滥发货币导致崩溃」时用这条量化。',
215
+ keys: ['通胀', '货币增长', 'inflation', '物价'],
216
+ frequency: 'high',
217
+ layer: 'phenomenological',
218
+ domains: ['economics'],
219
+ },
220
+ ]
@@ -0,0 +1,179 @@
1
+ /**
2
+ * 公式库第十二批:**生物学的通用公式(跨类群/跨尺度/跨世界)**。
3
+ *
4
+ * ## 为什么这一批值得单独成文件
5
+ *
6
+ * 库里原有 31 条生物公式,但**14 条是人类医学/毒理特化**:
7
+ * 血液酒精浓度、儿童剂量、孕期剩余天数、LD50、输液速率 ——
8
+ * 这些对「9000 吨工程生物」这类设定毫无用处,且参数标定与人类解剖强绑定。
9
+ *
10
+ * 这里补的是**任何生命形式都逃不掉的那一层**,判据五条全过:
11
+ *
12
+ * ```
13
+ * ① 跨类群 不依赖人类解剖
14
+ * ② 跨尺度 微生物到鲸,跨 10 个数量级
15
+ * ③ 跨世界 常数显式暴露,形式与地球无关
16
+ * ④ 有根源 物理/几何/化学/数学,不是拟合的经验系数
17
+ * ⑤ 参数可得 不需要查专业表就能填
18
+ * ```
19
+ *
20
+ * 第 ⑤ 条最易忽略:**通用幂律数学上能算任何东西,但用户不知道指数填多少,它就成了空壳**。
21
+ * 所以本批凡是幂律类,指数都**内置理论值**并注明出处,不留给用户填。
22
+ *
23
+ * ## 指数从哪来(分清「几何必然」与「网络理论推论」)
24
+ *
25
+ * 这个区分很重要 —— 前者在任何世界都成立(含非碳基),后者依赖「存在分形输运系统」这个前提:
26
+ *
27
+ * ```
28
+ * 2/3 ← 面积/体积的纯几何(无需任何生物假设)
29
+ * 1/4 ← 分形输运网络的最优层级(West-Brown-Enquist)
30
+ * 3/4 ← 分形网络的能量通量(Damuth)
31
+ * ```
32
+ *
33
+ * @module dsh-math-calculator/formula-library-biol-general
34
+ */
35
+
36
+ import type { FormulaDef } from './formula-library.js'
37
+
38
+ const p = (value: number, desc: string) => ({ value, desc })
39
+ const req = (desc: string) => ({ value: null, desc })
40
+
41
+ /** 生物通用公式 7 条。 */
42
+ export const BIO_GENERAL_FORMULAS: FormulaDef[] = [
43
+ {
44
+ id: 'sa_volume_ratio',
45
+ cat: 'biology/scaling',
46
+ name: '表面积-体积比(几何必然)',
47
+ expr: 'surface_coeff * mass^(2/3)',
48
+ params: {
49
+ mass: req('体重 kg'),
50
+ surface_coeff: p(0.11, '表面积系数(人体约 0.11,即 A ≈ 0.11·M^(2/3))'),
51
+ },
52
+ unit: 'm²(表面积;除以 mass 即单位体重的表面积)',
53
+ desc: ['A ∝ M^(2/3),故「面积/体积」∝ M^(−1/3)。**指数 2/3 是纯几何** ——', '任何三维物体体积 ∝ L³、表面积 ∝ L² 都成立,不需要任何生物假设,', '故跨物种、跨世界、甚至非碳基生命都适用。', '★ 一个式子回答巨兽设定的两个核心问题:体重 ×8 → 表面积 ×4 →', '单位体重散热能力 ÷2(为什么会过热);鳃面积同样只 ×4 → 单位体重摄氧 ÷2(为什么会缺氧)。', '★ Du Bois 那种人体经验式(body_surface_area)外推到 9000 吨必错;', '但这条给的是比例关系 —— 而判断大尺度是否可行,靠的就是比例。'].join(''),
54
+ keys: ['表面积', '体积比', 'SA:V', '平方立方', '巨兽', '表面'],
55
+ frequency: 'high',
56
+ layer: 'fundamental',
57
+ domains: ['biology'],
58
+ },
59
+ {
60
+ id: 'fick_diffusion',
61
+ cat: 'biology/transport',
62
+ name: '菲克扩散通量(呼吸/吸收/供氧)',
63
+ expr: 'diffusion_coeff * area * delta_conc / thickness',
64
+ params: {
65
+ diffusion_coeff: req('扩散系数 D m²/s(O₂ 在水中 2e-9、在空气中 2e-5)'),
66
+ area: req('交换面积 m²'),
67
+ delta_conc: req('浓度差或分压差(与 D 单位配套)'),
68
+ thickness: req('屏障厚度 m'),
69
+ },
70
+ unit: '物质通量(与 ΔC·D 的单位配套)',
71
+ desc: ['J = D·A·ΔC/δ。鳃、肺、皮肤吸收、根吸水、细胞膜运输全是同一个式子。', '★ 纯物理,不含物种假设;它也是鳃氧限制理论(GOLT)的根基。', '★ 关键推论:A ∝ M^(2/3)、δ ∝ M^(1/3),故供氧能力 ∝ M^(1/3) ——', '这就是「大生物必须换气而不能靠扩散」的定量证明。', '★ 与 diffusion_time 配合:前者给通量,后者给「扩散够不够快」。'].join(''),
72
+ keys: ['Fick', '菲克', '扩散', '供氧', '鳃', '通量'],
73
+ frequency: 'high',
74
+ layer: 'fundamental',
75
+ domains: ['biology'],
76
+ },
77
+ {
78
+ id: 'diffusion_time',
79
+ cat: 'biology/transport',
80
+ name: '扩散时间标度(尺寸上限的判据)',
81
+ expr: 'distance^2 / (2 * diffusion_coeff)',
82
+ params: {
83
+ distance: req('特征长度 m'),
84
+ diffusion_coeff: req('扩散系数 D m²/s(蛋白在水中约 1e-10)'),
85
+ },
86
+ unit: 's(扩散跨越该距离所需时间)',
87
+ desc: ['t ≈ Δx²/(2D)。布朗运动的数学结论,与是不是生物无关。', '★ 尺寸上限的直接判据(蛋白在水里 D≈1e-10):', '1 µm → 5 ms | 10 µm → 0.5 s(细胞尺度,够用)| 1 mm → 83 min(已不够)|', '1 m → 158 年(不可能)—— 所以大生物必须有循环系统。', '★ 这条能立刻否掉「靠扩散供氧的巨兽」方案,且用的是硬数学而非估计。'].join(''),
88
+ keys: ['扩散时间', 'diffusion time', '尺寸上限', '布朗运动', '循环系统'],
89
+ frequency: 'high',
90
+ layer: 'derived',
91
+ domains: ['biology'],
92
+ },
93
+ {
94
+ id: 'peclet_number',
95
+ cat: 'biology/transport',
96
+ name: '佩克莱数(扩散 vs 对流)',
97
+ expr: 'length * velocity / diffusion_coeff',
98
+ params: {
99
+ length: req('特征长度 m'),
100
+ velocity: req('流速 m/s'),
101
+ diffusion_coeff: req('扩散系数 D m²/s'),
102
+ },
103
+ unit: '无量纲(Pe ≪ 1 扩散主导;Pe ≫ 1 必须对流)',
104
+ desc: ['Pe = L·u/D。判断「靠扩散够不够、必须不必须对流」的无量纲判据。', '★ 它是 Reynolds 数的**质量输运版**:库里已有 reynolds(动量输运),', '但缺质量输运的对应物 —— 两者成对才有意义。', '★ 实例:毛细血管里血蛋白 v≈0.3 mm/s,微秒尺度扩散位移仅 0.3 nm → 扩散主导;', '大血管 u≈m/s → 对流主导。这解释了为什么微循环靠扩散、大循环必须泵。'].join(''),
105
+ keys: ['Peclet', '佩克莱', '无量纲', '对流扩散', '质量输运'],
106
+ frequency: 'medium',
107
+ layer: 'derived',
108
+ domains: ['biology', 'physics'],
109
+ },
110
+ {
111
+ id: 'hardy_weinberg',
112
+ cat: 'biology/genetics',
113
+ name: '哈迪-温伯格基因型频率',
114
+ expr: 'p^2 + 2 * p * q + q^2',
115
+ params: {
116
+ p: req('等位基因 A 频率(0—1)'),
117
+ q: req('等位基因 a 频率(0—1)'),
118
+ },
119
+ unit: '无量纲(应恒为 1,用于自检与分项计算)',
120
+ desc: ['p² + 2pq + q² = 1。**这是纯概率论结论**(配子随机组合),不需要任何生物机制,', '所以只要「存在等位基因 + 随机交配」它就成立 —— 跨世界完全通用。', '★ 常算的是中间项 2pq:隐性基因的携带者比例。例 p=0.9、q=0.1 → 2pq=18%,', '即每 100 人里有 18 个携带者,而表现型只有 q²=1%。', '★ 若结果明显不等于 1,说明 p+q≠1,输入有误 —— 这是一条自带自检的公式。'].join(''),
121
+ keys: ['Hardy-Weinberg', '哈迪温伯格', '基因频率', '遗传平衡', '携带者'],
122
+ frequency: 'high',
123
+ layer: 'fundamental',
124
+ domains: ['biology'],
125
+ },
126
+ {
127
+ id: 'gompertz_growth',
128
+ cat: 'biology/growth',
129
+ name: 'Gompertz 生长曲线',
130
+ expr: 'asymptote * exp(-shape * exp(-rate * t))',
131
+ params: {
132
+ asymptote: req('渐近上限(最终体型/规模)'),
133
+ shape: req('形状参数 b(决定曲线起点位置)'),
134
+ rate: req('早期增长率 c(1/时间)'),
135
+ t: req('时间'),
136
+ },
137
+ unit: '与 asymptote 同单位',
138
+ desc: ['N(t) = a·exp(−b·exp(−c·t))。描述「越大长得越慢」的通用形式。', '★ 比指数与 logistic 更贴合**有机体**的真实生长(logistic 更偏种群):', '跨动物、植物、细菌、肿瘤都拟合良好。', '★ 三个参数的物理含义:a = 渐近上限,c = 早期增长率,b = 曲线形状与起点。', '★ 跨世界照用:形式通用,系数按对象标定。'].join(''),
139
+ keys: ['Gompertz', '冈珀茨', '生长曲线', '渐近', '生长'],
140
+ frequency: 'high',
141
+ layer: 'phenomenological',
142
+ domains: ['biology'],
143
+ },
144
+ {
145
+ id: 'michaelis_menten',
146
+ cat: 'biology/kinetics',
147
+ name: '米氏方程(饱和动力学)',
148
+ expr: 'vmax * substrate / (km + substrate)',
149
+ params: {
150
+ vmax: req('最大速率 Vmax'),
151
+ km: req('半饱和常数 Km(速率达 Vmax/2 时的底物浓度)'),
152
+ substrate: req('底物浓度 S'),
153
+ },
154
+ unit: '与 vmax 同单位(速率)',
155
+ desc: ['v = Vmax·S/(Km+S)。一条式子覆盖五种现象:酶动力学、载体转运、', 'Holling II 型摄食功能反应、受体结合、土壤养分吸收 —— 因为它们都是同一个', '「先线性、再饱和」的双曲线形式。', '★ 结构性质:S ≪ Km 时 v ≈ (Vmax/Km)·S(线性);S ≫ Km 时 v → Vmax(饱和)。', '这个「处理能力有限必然饱和」是所有系统的共同特征。', '★ 与 q10_temperature 配合:前者管底物,后者管温度上限。'].join(''),
156
+ keys: ['Michaelis', '米氏', 'Km', 'Vmax', '酶', '饱和', 'Holling'],
157
+ frequency: 'high',
158
+ layer: 'fundamental',
159
+ domains: ['biology'],
160
+ },
161
+ {
162
+ id: 'q10_temperature',
163
+ cat: 'biology/kinetics',
164
+ name: 'Q₁₀ 温度系数(生物代谢的温度律)',
165
+ expr: 'rate_ref * q10^((temperature - temp_ref) / 10)',
166
+ params: {
167
+ rate_ref: req('参考温度下的速率'),
168
+ q10: p(2.5, 'Q₁₀ 系数(跨类群常见 2~3)'),
169
+ temperature: req('实际温度'),
170
+ temp_ref: req('参考温度'),
171
+ },
172
+ unit: '与 rate_ref 同单位(速率)',
173
+ desc: ['R(T) = R₀·Q₁₀^((T−T₀)/10)。温度每升高 10 度,速率变为 Q₁₀ 倍。', '★ 这是**生物系统实际使用的温度律**:生态模型几乎都用它,', '因为不需要活化能 Ea(那要查专业表)。', '★ 与库里已有的 arrhenius(化学式,要 Ea)并存而不替代:', '化学场景用 Arrhenius,生物场景用 Q₁₀ —— 同一条规律的两种学科参数化。', '★ Q₁₀ 跨类群很稳(2~3),这是它比 Arrhenius 更好用的原因。'].join(''),
174
+ keys: ['Q10', '温度系数', '代谢温度', '温度律', '生态模型'],
175
+ frequency: 'high',
176
+ layer: 'phenomenological',
177
+ domains: ['biology'],
178
+ },
179
+ ]