3.2 二元分离 — QueryEngine vs query.ts

3.2 二元分离 — QueryEngine vs query.ts

源码文件QueryEngine.ts(1296行)、query.ts(1729行)、query/config.ts(46行)、query/deps.ts(40行)、query/tokenBudget.ts(93行)

核心概念:二元分离架构、生命周期不对称性、状态所有权矩阵、接口边界设计、状态转换机制、分级错误恢复、Withheld 错误暂存、函数式状态更新


导语:一个架构决策的分量

原书第 3.2 节开宗明义:

“在深入查询循环的细节之前,我们需要先理解 Claude Code 引擎层最重要的一个架构决策:将引擎拆分为两个独立的抽象层级。”

这不是一句客套话。在整个第 3 章中,二元分离是所有后续讨论的基石——while(true) 循环、AsyncGenerator 流式驱动、配置快照模式、分级错误恢复——每一个机制都运行在这两个抽象层级的某一个之上,或者横跨两者之间的接口。理解了二元分离,就拿到了理解整个引擎层的钥匙。

本篇笔记将从源码出发,深入验证原书的描述,并重点关注状态转换错误恢复这两个最能体现二元分离价值的机制。


一、二元分离的本质:生命周期的不对称性

1.1 原书的论述

原书 3.2.1 节用了一个场景来说明为什么需要两层抽象:

会话开始
用户输入 #1 → query() 循环 #1 开始 → Ctrl+C 中断 → 循环 #1 结束
  (消息历史保留,累计用量保留)
用户输入 #2 → query() 循环 #2 开始 → 正常完成 → 循环 #2 结束
  (消息历史增长,累计用量增加)
用户输入 #3 → query() 循环 #3 开始 → ...

核心论点是:QueryEngine 的状态贯穿整个会话,而 query() 循环的状态每次用户输入都会重新创建。如果把这两种生命周期不同的状态混在一个对象中,会面临两个问题:

  1. 重置的范围模糊:中断后重新输入时,哪些状态该重置、哪些该保留?
  2. 复用的粒度不匹配:query() 需要被多个调用方复用(REPL、子 Agent、SDK),如果绑定到 QueryEngine 实例,复用就需要创建完整实例。

1.2 源码验证

源码完美印证了这一设计。QueryEngine 的类注释(第 176-183 行)写道:

/**
 * QueryEngine owns the query lifecycle and session state for a conversation.
 * It extracts the core logic from ask() into a standalone class that can be
 * used by both the headless/SDK path and (in a future phase) the REPL.
 *
 * One QueryEngine per conversation. Each submitMessage() call starts a new
 * turn within the same conversation. State (messages, file cache, usage, etc.)
 * persists across turns.
 */

注意注释中的关键信息:

  • “One QueryEngine per conversation” —— 会话级生命周期
  • “Each submitMessage() call starts a new turn” —— 每次输入是新一轮
  • “State persists across turns” —— 状态跨轮次持久
  • “extracts the core logic from ask()” —— 暗示了演进历史(PR#22546 的重构)

query() 函数的签名(query.ts:219-228)则体现了轮次级生命周期:

export async function* query(
  params: QueryParams,
): AsyncGenerator<
  | StreamEvent
  | RequestStartEvent
  | Message
  | TombstoneMessage
  | ToolUseSummaryMessage,
  Terminal
> {
  const consumedCommandUuids: string[] = []
  const terminal = yield* queryLoop(params, consumedCommandUuids)
  // ...命令生命周期通知
  return terminal
}

query() 是一个纯函数式的异步生成器——接收所有上下文作为参数(QueryParams),不持有任何跨调用状态。每次 submitMessage() 调用它时,都会创建全新的 State 对象:

// query.ts:268-279 —— 每次进入 queryLoop 都创建全新 State
let state: State = {
  messages: params.messages,          // 从 QueryEngine 传入的快照
  toolUseContext: params.toolUseContext,
  maxOutputTokensOverride: params.maxOutputTokensOverride,
  autoCompactTracking: undefined,     // ← 每次重置
  stopHookActive: undefined,          // ← 每次重置
  maxOutputTokensRecoveryCount: 0,    // ← 每次重置
  hasAttemptedReactiveCompact: false, // ← 每次重置
  turnCount: 1,                       // ← 每次重置
  pendingToolUseSummary: undefined,   // ← 每次重置
  transition: undefined,              // ← 每次重置
}

1.3 演进历史:ask() 到 QueryEngine + query() 的重构

原书提到了一次关键重构:

“事实上,query() 的提取正是 Claude Code 演进过程中的一次关键重构(PR#22546)——QueryEngine 最初的 ask() 方法包含了整个查询循环,随着系统需要支持 Headless、SDK、REPL 等多种调用模式,将查询循环提取为独立函数成了必然选择。”

源码中 ask() 函数(第 1186-1295 行)正是这个重构的遗留物——它是一个便利包装器,内部创建新的 QueryEngine 实例然后调用 submitMessage()

export async function* ask({...}): AsyncGenerator<SDKMessage, void, unknown> {
  const engine = new QueryEngine({
    cwd,
    tools,
    commands,
    // ...30+ 个配置字段
    initialMessages: mutableMessages,
    readFileCache: cloneFileStateCache(getReadFileCache()),
    // ...
  })

  try {
    yield* engine.submitMessage(prompt, { uuid: promptUuid, isMeta })
  } finally {
    setReadFileCache(engine.getReadFileState())  // 读回文件缓存
  }
}

ask() 的存在证明了二元分离的第二个理由——复用粒度不匹配。当只需要执行一次性查询时,不必手动管理 QueryEngine 生命周期,ask() 内部自动创建和销毁实例。但 ask() 内部仍然调用了 query()(通过 submitMessage()for await (const message of query({...}))),这证明了 query() 作为独立函数的可复用性。


二、接口边界设计

2.1 QueryEngine 的对外接口

原书 3.2.2 节描述了 QueryEngine 的两个核心方法。源码中实际暴露的接口更丰富:

export class QueryEngine {
  // 核心方法
  async *submitMessage(
    prompt: string | ContentBlockParam[],
    options?: { uuid?: string; isMeta?: boolean },
  ): AsyncGenerator<SDKMessage, void, unknown>

  // 中断当前查询
  interrupt(): void

  // 状态访问器
  getMessages(): readonly Message[]
  getReadFileState(): FileStateCache
  getSessionId(): string
  setModel(model: string): void
}

submitMessage() 的八步管线(原书 3.6.1 节详述,这里从接口边界视角概述):

步骤代码位置职责与 query() 的关系
1. 初始化第 238-241 行清理技能集合、设置 Cwd、记录时间戳会话级准备
2. 权限包装第 244-271 行canUseTool 包装为带拒绝追踪版本装饰器模式,不侵入 query()
3. 系统提示构建第 284-325 行fetchSystemPromptParts()asSystemPrompt()产出 systemPrompt 传给 query()
4. 用户输入处理第 410-428 行processUserInput() 解析斜杠命令、附件产出 messages 传给 query()
5. 消息持久化第 450-463 行recordTranscript() 写入 JSONL在 query() 启动前保证可恢复
6. 技能/插件加载第 534-538 行Promise.all([getSlashCommandToolSkills, loadAllPluginsCacheOnly])并行加载,不阻塞
7. 查询执行第 675-1048 行for await (const message of query({...}))核心边界
8. 结果返回第 1058-1156 行提取文本结果、构建 SDK result 消息后处理

第 7 步是最关键的接口边界——QueryEngine 通过 for await...of 消费 query() 的输出,逐条处理消息并 yield 给上层调用方。

2.2 query() 的函数签名

原书给出了简化的签名,实际源码中的 QueryParams 类型更完整:

export type QueryParams = {
  messages: Message[]              // 历史消息(从 QueryEngine 传入的快照)
  systemPrompt: SystemPrompt       // 系统提示
  userContext: { [k: string]: string }  // 用户上下文
  systemContext: { [k: string]: string } // 系统上下文
  canUseTool: CanUseToolFn         // 权限检查函数(已包装)
  toolUseContext: ToolUseContext   // 工具上下文
  fallbackModel?: string           // 备用模型
  querySource: QuerySource         // 调用来源标记
  maxOutputTokensOverride?: number // 最大输出 Token 覆盖
  maxTurns?: number                // 最大轮次限制
  skipCacheWrite?: boolean         // 跳过缓存写入
  taskBudget?: { total: number }   // API 任务预算
  deps?: QueryDeps                 // 可选的依赖注入
}

原书评价道:

“注意 query() 的设计哲学:它接收所有必要的上下文作为参数,而不是从全局状态中读取。这使得 query() 成为一个(几乎)纯函数式的异步生成器——给定相同的输入参数,它的行为是确定的。”

源码中的 queryLoop() 函数完美体现了这一点(第 252-279 行):

async function* queryLoop(
  params: QueryParams,
  consumedCommandUuids: string[],
): AsyncGenerator<...> {
  // Immutable params — never reassigned during the query loop.
  const {
    systemPrompt,
    userContext,
    systemContext,
    canUseTool,
    fallbackModel,
    querySource,
    maxTurns,
    skipCacheWrite,
  } = params
  const deps = params.deps ?? productionDeps()  // 依赖注入默认值

  // Mutable cross-iteration state. ← 轮次级状态
  let state: State = { ... }

const 解构 params 的细节值得注意——params 的内容在循环期间不会被重新赋值,这确保了查询参数的不可变性。真正可变的是 state(轮次级状态)和 toolUseContext(工具上下文),它们在循环迭代中更新。


三、状态所有权矩阵(完整版)

3.1 原书矩阵的扩展

原书 3.2.3 节给出了一个状态所有权矩阵。通过逐行检查源码,我们得到了更完整的版本:

QueryEngine 拥有的状态(会话级)
状态字段源码位置类型生命周期说明
mutableMessagesQueryEngine.ts:186Message[]完整对话历史,跨轮次累积
totalUsageQueryEngine.ts:189NonNullableUsage累计 token 使用量(输入/输出/缓存)
permissionDenialsQueryEngine.ts:188SDKPermissionDenial[]权限拒绝记录,供 SDK 报告
readFileStateQueryEngine.ts:191FileStateCache文件读取缓存(文件内容哈希等)
discoveredSkillNamesQueryEngine.ts:197Set<string>已发现的技能名称集合(每次 submitMessage 清空
loadedNestedMemoryPathsQueryEngine.ts:198Set<string>已加载的嵌套记忆路径
hasHandledOrphanedPermissionQueryEngine.ts:190boolean孤儿权限处理标记(仅处理一次)
abortControllerQueryEngine.ts:187AbortController中断控制器(会话级,但每次中断后状态保留)
query() 拥有的状态(轮次级)
状态字段源码位置类型生命周期说明
messages (State)query.ts:205Message[]当前轮消息快照(从 params 传入,循环中追加)
turnCountquery.ts:213number当前轮次计数,从 1 开始递增
transitionquery.ts:216Continue | undefined"为什么回到循环顶部"的原因标签
maxOutputTokensRecoveryCountquery.ts:208numberMax Output Tokens 错误恢复计数(上限 3)
hasAttemptedReactiveCompactquery.ts:209boolean是否已尝试响应式压缩(防死循环)
maxOutputTokensOverridequery.ts:210number | undefined输出 Token 限制覆盖值
autoCompactTrackingquery.ts:207AutoCompactTrackingState | undefined自动压缩跟踪状态
pendingToolUseSummaryquery.ts:211Promise<ToolUseSummaryMessage | null> | undefined待处理的工具使用摘要 Promise
stopHookActivequery.ts:212boolean | undefinedStop Hook 是否激活
budgetTrackerquery.ts:280BudgetTracker | nullToken 预算追踪器(循环局部变量)
taskBudgetRemainingquery.ts:291number | undefined任务预算剩余(循环局部变量)

3.2 状态隔离的关键设计

discoveredSkillNames 的特殊处理值得特别关注。它是 QueryEngine 的成员变量,但每次 submitMessage() 调用时会清空:

// QueryEngine.ts:238
this.discoveredSkillNames.clear()

注释解释了原因:

// Turn-scoped skill discovery tracking (feeds was_discovered on
// tengu_skill_tool_invocation). Must persist across the two
// processUserInputContext rebuilds inside submitMessage, but is cleared
// at the start of each submitMessage to avoid unbounded growth across
// many turns in SDK mode.

这是一个跨两个抽象层级的混合生命周期状态——它在单次 submitMessage() 内需要跨 processUserInputContext 重建持久,但在多次 submitMessage() 之间需要重置。这种状态不适合放在 query()State 中(因为 processUserInputContext 在调用 query() 之前就构建了),也不适合纯会话级持久(会导致无限增长)。


四、状态转换机制:二元分离的核心价值

4.1 两层状态流转的全景

二元分离在状态转换方面的核心价值是:QueryEngine 负责状态在轮次间的持久化,query() 负责状态在单轮内的转换

┌─────────────────────────────────────────────────────────────────┐
│                      QueryEngine (会话级)                        │
│                                                                 │
│  mutableMessages ──────┐                                       │
│  totalUsage            │  submitMessage() 八步管线               │
│  permissionDenials     │                                       │
│  readFileState         │  ┌─────────────────────────────────┐   │
│  discoveredSkillNames  │  │      query() (轮次级)            │   │
│                        │  │                                 │   │
│                        └──│  State {                         │   │
│                           │    messages: [...snapshot]       │   │
│                           │    turnCount: 1                  │   │
│                           │    transition: undefined          │   │
│                           │    ...                           │   │
│                           │  }                                │   │
│                           │                                 │   │
│                           │  while (true) {                  │   │
│                           │    // 六阶段循环体               │   │
│                           │    // 通过 continue/return 转换  │   │
│                           │  }                                │   │
│                           │                                 │   │
│                           │  yield: StreamEvent / Message    │   │
│                           │  return: Terminal                │   │
│                           └─────────────────────────────────┘   │
│                                     │                           │
│  for await (message of query({...})) │                           │
│    ← 消费流式消息 ───────────────────┘                           │
│    ← 更新 mutableMessages、totalUsage 等                         │
│    ← yield SDKMessage 给上层                                      │
│                                                                 │
│  return: SDK result (success/error)                             │
└─────────────────────────────────────────────────────────────────┘

4.2 状态从 QueryEngine 到 query() 的传递

submitMessage() 在第 6 步将 QueryEngine 的会话级状态"快照"传给 query()

// QueryEngine.ts:675-686
for await (const message of query({
  messages,                          // ← 从 mutableMessages 复制的快照
  systemPrompt,                      // ← 会话级构建的系统提示
  userContext,                       // ← 会话级用户上下文
  systemContext,                     // ← 会话级系统上下文
  canUseTool: wrappedCanUseTool,     // ← 已包装的权限函数
  toolUseContext: processUserInputContext, // ← 工具上下文
  fallbackModel,                     // ← 备用模型
  querySource: 'sdk',                // ← 调用来源
  maxTurns,                          // ← 最大轮次限制
  taskBudget,                        // ← 任务预算
})) {

注意 messages 的来源:

// QueryEngine.ts:434
const messages = [...this.mutableMessages]  // ← 浅拷贝快照

这是二元分离的精髓——query() 获得的是 mutableMessages快照,不是引用。query() 在循环中追加的 assistant/user/progress 消息先存入局部 messages 数组,然后逐条 yield 回 submitMessage(),由后者决定是否 push 到 mutableMessages

4.3 状态从 query() 回流到 QueryEngine

submitMessage()for await 循环体(第 687-968 行)是状态回流的关键。每种消息类型有不同的回流策略:

for await (const message of query({...})) {
  // 1. 持久化:assistant/user/compact_boundary 写入 transcript
  if (message.type === 'assistant' || message.type === 'user' ||
      (message.type === 'system' && message.subtype === 'compact_boundary')) {
    messages.push(message)
    if (persistSession) {
      if (message.type === 'assistant') {
        void recordTranscript(messages)    // ← fire-and-forget
      } else {
        await recordTranscript(messages)   // ← await
      }
    }
  }

  // 2. 回流到 mutableMessages(按类型区分)
  switch (message.type) {
    case 'assistant':
      this.mutableMessages.push(message)   // ← 回流到会话级状态
      yield* normalizeMessage(message)     // ← 转发给 SDK 调用方
      break
    case 'user':
      this.mutableMessages.push(message)   // ← 回流到会话级状态
      yield* normalizeMessage(message)
      break
    case 'progress':
      this.mutableMessages.push(message)   // ← 进度消息也回流
      if (persistSession) {
        messages.push(message)
        void recordTranscript(messages)
      }
      yield* normalizeMessage(message)
      break
    case 'stream_event':
      // stream_event 不回流到 mutableMessages
      // 但 usage 信息回流到 totalUsage
      if (message.event.type === 'message_stop') {
        this.totalUsage = accumulateUsage(
          this.totalUsage,                  // ← 回流到会话级状态
          currentMessageUsage,
        )
      }
      break
    case 'system':
      // system 消息选择性回流
      if (message.subtype === 'compact_boundary') {
        this.mutableMessages.push(message)
        // 释放压缩前的消息(GC 优化)
        const boundaryIdx = this.mutableMessages.length - 1
        if (boundaryIdx > 0) {
          this.mutableMessages.splice(0, boundaryIdx)
        }
      }
      break
  }
}

关键发现:并非所有 query() 的输出都回流到 mutableMessagesstream_event 只回流 usage 信息到 totalUsage,消息本身不持久化。tombstone 消息被完全跳过(控制信号,不是数据)。这种细粒度的回流策略正是二元分离的价值——QueryEngine 可以根据会话级需求决定保留什么,而不影响 query() 的循环逻辑。

4.4 State 对象的函数式更新

原书 3.6.3 节描述了 queryLoop 中的函数式更新模式:

“状态更新始终是创建新对象,而不是修改旧对象”

源码中每个 continue 点都用 {...state, ...updates} 创建新 State 对象。以 next_turn(最常见的转换)为例:

// query.ts:1715-1727
const next: State = {
  messages: [...messagesForQuery, ...assistantMessages, ...toolResults],
  toolUseContext: toolUseContextWithQueryTracking,
  autoCompactTracking: tracking,
  turnCount: nextTurnCount,                // ← 递增
  maxOutputTokensRecoveryCount: 0,         // ← 重置
  hasAttemptedReactiveCompact: false,      // ← 重置
  pendingToolUseSummary: nextPendingToolUseSummary,
  maxOutputTokensOverride: undefined,      // ← 重置
  stopHookActive,
  transition: { reason: 'next_turn' },     // ← 设置转换原因
}
state = next

原书也指出了务实的 Trade-off:

“这种’函数式’是有限度的——state.messages 数组本身是可变的(push() 操作),因为完全不可变的消息数组在频繁追加时会产生大量 GC 压力。”

源码验证了这一点。messages 在循环内部使用 push()[...array] 混合策略:

// 不可变:创建新数组
messages: [...messagesForQuery, ...assistantMessages, ...toolResults]

// 可变:在循环体内部
assistantMessages.push(message)    // query.ts:827
toolResults.push(...)              // query.ts:854

五、分级错误恢复:二元分离的最佳佐证

错误恢复是体现二元分离价值的最有力场景。原书 3.8 节的核心论点是:

“不同错误的恢复方向可能完全相反……如果对这四种错误都执行’减少输入’的操作,Max Output Tokens 的恢复反而会让情况变糟。”

5.1 错误恢复的层级归属

二元分离将错误处理清晰地分成了两层:

层级错误类型处理方式源码位置
query() 层Prompt Too Long (413)Context Collapse → Reactive Compactquery.ts:1085-1183
query() 层Max Output Tokens升级 64K → 多轮恢复消息(上限 3 次)query.ts:1188-1256
query() 层Model UnavailableFallbackTriggeredError → 切换备用模型query.ts:893-953
query() 层Media Size ErrorReactive Compact + 图片剥离query.ts:1119-1175
query() 层Image Error直接返回 { reason: 'image_error' }query.ts:970-978
query() 层Model Erroryield 错误消息 + 返回 { reason: 'model_error' }query.ts:955-997
query() 层Aborted (streaming)yield 中断消息 + 返回 { reason: 'aborted_streaming' }query.ts:1015-1052
query() 层Aborted (tools)yield 中断消息 + 返回 { reason: 'aborted_tools' }query.ts:1485-1516
query() 层Blocking Limityield PTL 错误 + 返回 { reason: 'blocking_limit' }query.ts:641-647
QueryEngine 层Max Turnsyield max_turns_reached attachment + returnQueryEngine.ts:842-874
QueryEngine 层USD Budget Exceededyield error_max_budget_usd result + returnQueryEngine.ts:972-1002
QueryEngine 层Structured Output Retry Limityield error result + returnQueryEngine.ts:1004-1048
QueryEngine 层Error During Executionyield error_during_execution result + returnQueryEngine.ts:1082-1118

关键发现query() 层处理的是可恢复错误——通过修改 State 并 continue 回到循环顶部重试。QueryEngine 层处理的是不可恢复错误——直接终止查询并返回错误结果。这种分层是二元分离的直接产物。

5.2 四种恢复路径的详细分析

路径 1:Prompt Too Long (413) — 两步递进恢复
// query.ts:1085-1183

// 第一步:Context Collapse Drain(轻量,保留粒度)
if (feature('CONTEXT_COLLAPSE') &&
    contextCollapse &&
    state.transition?.reason !== 'collapse_drain_retry') {  // ← 防重复
  const drained = contextCollapse.recoverFromOverflow(
    messagesForQuery, querySource)
  if (drained.committed > 0) {
    state = {
      messages: drained.messages,
      // ...保留其他状态
      transition: { reason: 'collapse_drain_retry', committed: drained.committed },
    }
    continue  // ← 回到循环顶部,用压缩后的消息重试
  }
}

// 第二步:Reactive Compact(重量,LLM 驱动摘要)
if ((isWithheld413 || isWithheldMedia) && reactiveCompact) {
  const compacted = await reactiveCompact.tryReactiveCompact({
    hasAttempted: hasAttemptedReactiveCompact,  // ← 防重复
    // ...
  })
  if (compacted) {
    state = {
      messages: postCompactMessages,
      hasAttemptedReactiveCompact: true,         // ← 标记已尝试
      transition: { reason: 'reactive_compact_retry' },
    }
    continue  // ← 回到循环顶部,用摘要后的消息重试
  }
  // 恢复失败——surface 被暂存的错误
  yield lastMessage
  return { reason: isWithheldMedia ? 'image_error' : 'prompt_too_long' }
}

二元分离的价值:错误恢复通过修改 Statecontinue 实现,而不是抛出异常。这使得恢复逻辑与正常循环逻辑在代码结构上完全对称——都是"修改 state → 设置 transition → continue"。QueryEngine 完全不需要知道错误发生了,它只看到 query() yield 了压缩边界消息和新的 assistant 消息。

路径 2:Max Output Tokens — 渐进式升级
// query.ts:1188-1256

// 恢复 1:升级 maxOutputTokens 到 64K(单次)
if (capEnabled &&
    maxOutputTokensOverride === undefined &&  // ← 未曾覆盖
    !process.env.CLAUDE_CODE_MAX_OUTPUT_TOKENS) {
  state = {
    ...state,
    maxOutputTokensOverride: ESCALATED_MAX_TOKENS,  // ← 升级到 64K
    transition: { reason: 'max_output_tokens_escalate' },
  }
  continue
}

// 恢复 2-3:发送恢复消息(最多 3 次)
if (maxOutputTokensRecoveryCount < MAX_OUTPUT_TOKENS_RECOVERY_LIMIT) {  // ← 上限 3
  const recoveryMessage = createUserMessage({
    content: 'Output token limit hit. Resume directly — no apology, no recap...',
    isMeta: true,
  })
  state = {
    ...state,
    messages: [...messagesForQuery, ...assistantMessages, recoveryMessage],
    maxOutputTokensRecoveryCount: maxOutputTokensRecoveryCount + 1,  // ← 递增
    transition: { reason: 'max_output_tokens_recovery', attempt: ... },
  }
  continue
}

// 恢复耗尽——surface 被暂存的错误
yield lastMessage
路径 3:Model Unavailable — 流式回退
// query.ts:893-953
} catch (innerError) {
  if (innerError instanceof FallbackTriggeredError && fallbackModel) {
    currentModel = fallbackModel              // ← 切换模型
    attemptWithFallback = true                // ← 重试标志

    // 清理失败的尝试
    yield* yieldMissingToolResultBlocks(assistantMessages, 'Model fallback triggered')
    assistantMessages.length = 0
    toolResults.length = 0
    toolUseBlocks.length = 0
    needsFollowUp = false

    // 创建新的流式执行器
    if (streamingToolExecutor) {
      streamingToolExecutor.discard()
      streamingToolExecutor = new StreamingToolExecutor(...)
    }

    // 更新模型
    toolUseContext.options.mainLoopModel = fallbackModel

    yield createSystemMessage(
      `Switched to ${renderModelName(...)} due to high demand...`, 'warning')

    continue  // ← 回到 while (attemptWithFallback) 循环
  }
  throw innerError  // ← 无法处理,抛出
}
路径 4:Stop Hook Blocking — 带防死循环保护的恢复
// query.ts:1282-1306
if (stopHookResult.blockingErrors.length > 0) {
  const next: State = {
    messages: [
      ...messagesForQuery,
      ...assistantMessages,
      ...stopHookResult.blockingErrors,  // ← 注入 Hook 错误消息
    ],
    maxOutputTokensRecoveryCount: 0,
    // ↓ 关键:保留 hasAttemptedReactiveCompact
    // 注释解释:如果 compact 已经运行且无法从 PTL 恢复,
    // 在 stop-hook 阻塞错误后重试会产生相同结果。
    // 重置为 false 会导致死循环:compact → 仍然太长 → 错误 →
    // stop hook blocking → compact → … 燃烧数千次 API 调用。
    hasAttemptedReactiveCompact,  // ← 不重置!防死循环
    stopHookActive: true,
    transition: { reason: 'stop_hook_blocking' },
  }
  state = next
  continue
}

5.3 Withheld 错误暂存机制

这是源码中一个原书未描述的重要机制。query() 不会立即将可恢复错误 yield 给调用方,而是先暂存(withhold),等恢复结果确定后再决定是否暴露:

// query.ts:799-825
let withheld = false

// 检查多种可恢复错误
if (feature('CONTEXT_COLLAPSE') &&
    contextCollapse?.isWithheldPromptTooLong(message, ...)) {
  withheld = true
}
if (reactiveCompact?.isWithheldPromptTooLong(message)) {
  withheld = true
}
if (mediaRecoveryEnabled &&
    reactiveCompact?.isWithheldMediaSizeError(message)) {
  withheld = true
}
if (isWithheldMaxOutputTokens(message)) {
  withheld = true
}

// 暂存的错误不 yield 给调用方,但仍推入 assistantMessages
// 以便后续恢复检查能找到它
if (!withheld) {
  yield yieldMessage
}
if (message.type === 'assistant') {
  assistantMessages.push(message)  // ← 无论是否暂存都 push
}

设计哲学:如果恢复成功,错误对调用方不可见——调用方只看到恢复后的正常响应。如果恢复失败,错误才被 yield lastMessage surface 给调用方。这避免了 SDK 消费者(如 desktop/cowork)在恢复期间看到中间态错误而误终止会话。

5.4 错误恢复作为状态转换的对称性

原书 3.8.3 节有一个精辟的总结:

“错误恢复不是一个特殊的代码路径,而是状态机的一个正常转换。压缩后的重试和正常的工具调用后继续,在代码结构上是完全对称的——都是’修改 state → 设置 transition → continue’。”

源码完美验证了这一点。对比两种 continue 路径:

// 正常工具调用后继续(next_turn)
state = {
  messages: [...messagesForQuery, ...assistantMessages, ...toolResults],
  turnCount: nextTurnCount,
  transition: { reason: 'next_turn' },
  // ...重置恢复相关状态
}

// 413 错误恢复后继续(reactive_compact_retry)
state = {
  messages: postCompactMessages,  // ← 压缩后的消息
  hasAttemptedReactiveCompact: true,  // ← 标记
  transition: { reason: 'reactive_compact_retry' },
  // ...保留其他状态
}

两者结构完全相同——都是创建新 State 对象、设置 transition reason、然后 continue。循环体不需要区分"正常继续"和"恢复后继续"——它只看 state.transition?.reason 来做分支决策(如跳过已尝试的压缩)。


六、QueryEngine 层的终止保护

除了 query() 层的五层终止保护(maxTurns、Token Budget、错误恢复上限、LLM 自然终止、外部中断),QueryEngine 层还额外实现了两层原书未描述的终止保护:

6.1 USD 预算检查

// QueryEngine.ts:972-1002
// 在 for await 循环体中,每消费一条消息后检查
if (maxBudgetUsd !== undefined && getTotalCost() >= maxBudgetUsd) {
  yield {
    type: 'result',
    subtype: 'error_max_budget_usd',
    is_error: true,
    errors: [`Reached maximum budget ($${maxBudgetUsd})`],
    total_cost_usd: getTotalCost(),
    usage: this.totalUsage,
    // ...
  }
  return  // ← 终止 submitMessage()
}

这个检查在 QueryEngine 层而不是 query() 层,因为成本追踪是会话级的——getTotalCost() 读取的是全局累计成本,不是单轮成本。

6.2 结构化输出重试限制

// QueryEngine.ts:1004-1048
if (message.type === 'user' && jsonSchema) {
  const currentCalls = countToolCalls(
    this.mutableMessages,
    SYNTHETIC_OUTPUT_TOOL_NAME,
  )
  const callsThisQuery = currentCalls - initialStructuredOutputCalls
  const maxRetries = parseInt(
    process.env.MAX_STRUCTURED_OUTPUT_RETRIES || '5', 10)
  if (callsThisQuery >= maxRetries) {
    yield {
      type: 'result',
      subtype: 'error_max_structured_output_retries',
      errors: [`Failed to provide valid structured output after ${maxRetries} attempts`],
    }
    return
  }
}

这个检查也在 QueryEngine 层,因为它需要访问 this.mutableMessages(会话级消息历史)来计算结构化输出工具的调用次数。

6.3 七层终止保护全景

层级保护机制检查位置条件
query() 层maxTurnsquery.ts:1705nextTurnCount > maxTurns
query() 层Token Budgetquery.ts:1308-1355budgetTracker 递减收益检测
query() 层Max Output Tokens 恢复上限query.ts:1223recoveryCount >= 3
query() 层LLM 自然终止query.ts:1062!needsFollowUp(无工具调用)
query() 层外部中断query.ts:1015, 1485abortController.signal.aborted
QueryEngine 层USD 预算QueryEngine.ts:972getTotalCost() >= maxBudgetUsd
QueryEngine 层结构化输出重试QueryEngine.ts:1015callsThisQuery >= maxRetries

七、submitMessage 的消息处理状态机

QueryEngine.submitMessage()for await 循环体本身就是一个消息处理状态机。它维护了多个局部状态变量:

// QueryEngine.ts:657-673
let currentMessageUsage: NonNullableUsage = EMPTY_USAGE  // 当前消息 usage
let turnCount = 1                                         // 轮次计数
let hasAcknowledgedInitialMessages = false                // 初始消息确认标记
let structuredOutputFromTool: unknown                     // 结构化输出
let lastStopReason: string | null = null                  // 最后的 stop_reason
const errorLogWatermark = getInMemoryErrors().at(-1)      // 错误日志水位线
const initialStructuredOutputCalls = jsonSchema           // 结构化输出调用基线
  ? countToolCalls(this.mutableMessages, SYNTHETIC_OUTPUT_TOOL_NAME)
  : 0

errorLogWatermark 是一个精妙的设计——它记录了查询开始前最后一条错误日志的引用。查询结束后,如果触发了 error_during_executionerrors[] 数组只包含水位线之后的错误(即本次查询的错误),而不是整个进程的错误历史。注释解释了为什么用引用而不是索引:

// Reference-based watermark so error_during_execution's errors[] is
// turn-scoped. A length-based index breaks when the 100-entry ring buffer
// shift()s during the turn — the index slides. If this entry is rotated
// out, lastIndexOf returns -1 and we include everything (safe fallback).

八、Compact Boundary 的跨层协调

上下文压缩(Compact)是二元分离中最复杂的跨层协调场景。压缩发生时,query()QueryEngine 必须同步更新各自的状态。

8.1 query() 层的压缩触发

query() 在循环体第一阶段执行四层压缩管线:

applyToolResultBudget → Snip 压缩 → 微压缩 → Context Collapse → 自动压缩

压缩成功后,query() yield 一组 postCompactMessages

// query.ts:528-535
const postCompactMessages = buildPostCompactMessages(compactionResult)
for (const message of postCompactMessages) {
  yield message  // ← yield 给 QueryEngine
}
messagesForQuery = postCompactMessages  // ← 更新本地状态

8.2 QueryEngine 层的压缩响应

QueryEngine 收到 compact_boundary 系统消息后,执行会话级状态清理:

// QueryEngine.ts:897-942
case 'system': {
  // Snip 边界处理
  const snipResult = this.config.snipReplay?.(message, this.mutableMessages)
  if (snipResult !== undefined) {
    if (snipResult.executed) {
      this.mutableMessages.length = 0              // ← 清空
      this.mutableMessages.push(...snipResult.messages) // ← 重建
    }
    break
  }

  this.mutableMessages.push(message)  // ← 添加 compact_boundary

  // Compact Boundary:释放压缩前的消息
  if (message.subtype === 'compact_boundary') {
    const boundaryIdx = this.mutableMessages.length - 1
    if (boundaryIdx > 0) {
      this.mutableMessages.splice(0, boundaryIdx)  // ← GC 优化
    }
    // 同步清理局部 messages 数组
    const localBoundaryIdx = messages.length - 1
    if (localBoundaryIdx > 0) {
      messages.splice(0, localBoundaryIdx)
    }

    yield {  // ← 转发给 SDK 调用方
      type: 'system',
      subtype: 'compact_boundary',
      compact_metadata: toSDKCompactMetadata(message.compactMetadata),
    }
  }
}

关键设计mutableMessages 在压缩后只保留 compact_boundary 及之后的消息,压缩前的消息被 splice 移除以释放内存。但 query() 内部的 state.messages 仍然保留完整的压缩前历史(因为 query() 获得的是快照副本)。这种不对称是安全的——query() 在下一次 continue 时会用 state.messages(包含压缩后的消息)继续循环,不再引用压缩前的历史。


九、依赖注入:二元分离的测试策略

9.1 QueryDeps 的精准边界

原书 3.9 节讨论了依赖注入。query/deps.ts(40 行)定义了只有 4 个依赖的窄接口:

export type QueryDeps = {
  callModel: typeof queryModelWithStreaming   // LLM API 调用
  microcompact: typeof microcompactMessages   // 微压缩
  autocompact: typeof autoCompactIfNeeded     // 自动压缩
  uuid: () => string                           // UUID 生成
}

export function productionDeps(): QueryDeps {
  return {
    callModel: queryModelWithStreaming,
    microcompact: microcompactMessages,
    autocompact: autoCompactIfNeeded,
    uuid: randomUUID,
  }
}

注释明确解释了设计决策:

// I/O dependencies for query(). Passing a `deps` override into QueryParams
// lets tests inject fakes directly instead of spyOn-per-module — the most
// common mocks (callModel, autocompact) are each spied in 6-8 test files
// today with module-import-and-spy boilerplate.
//
// Using `typeof fn` keeps signatures in sync with the real implementations
// automatically.
//
// Scope is intentionally narrow (4 deps) to prove the pattern. Followup
// PRs can add runTools, handleStopHooks, logEvent, queue ops, etc.

9.2 二元分离对测试的影响

二元分离使测试策略自然分层:

测试层级目标Mock 策略典型用法
query() 单元测试验证循环逻辑注入 deps: { callModel: mockFn, ... }测试错误恢复、状态转换
QueryEngine 集成测试验证会话级状态Mock canUseTool,使用真实 query()测试权限追踪、消息持久化
ask() 端到端测试验证完整流程Mock API 层测试 SDK 输出格式

query() 的纯函数式设计(所有上下文通过参数传入)使得单元测试极其简单——不需要构造 QueryEngine 实例,不需要模拟会话状态,只需构造 QueryParams 和可选的 QueryDeps


十、子模块的单一职责

原书 3.10 节描述了四个子模块的分工。从二元分离的视角来看,这四个子模块都服务于 query() 层:

模块职责副作用与 QueryEngine 的关系
config.ts配置快照无(纯函数)独立于 QueryEngine
deps.ts依赖注入边界无(纯工厂函数)独立于 QueryEngine
tokenBudget.tsToken 预算决策修改 tracker(可变)独立于 QueryEngine
stopHooks.tsHook 编排yield 消息、触发后台任务独立于 QueryEngine

关键发现:所有四个子模块都不依赖 QueryEngine 的状态。它们要么是纯函数(config、deps),要么只依赖 query() 传入的 toolUseContext(tokenBudget、stopHooks)。这种独立性正是二元分离的产物——query() 不需要访问 QueryEngine 的会话级状态就能完成自己的工作。


十一、与其他框架的对比

原书 3.11 节比较了 Claude Code 与 LangChain/LangGraph 和 OpenAI Assistants API。从二元分离的视角,这些对比有了新的维度:

维度Claude CodeLangChain/LangGraphOpenAI Assistants
状态管理层级两层(QueryEngine + query)单层(Graph 节点间传递)服务端管理(Thread)
会话级状态QueryEngine 类成员无内建会话概念服务端 Thread
轮次级状态query() 的 State 对象Graph 状态服务端 Run
状态传递方式参数传递 + for await 消费Graph 边传递状态API 调用
错误恢复层级query() 层可恢复 + QueryEngine 层终止通常简单重试服务端处理
测试隔离query() 可独立测试(注入 deps)需要模拟 Graph需要 Mock API

Claude Code 的二元分离在架构上更接近React 的组件分层——QueryEngine 类似 React 的 Fiber 树(持久化状态),query() 类似 React 的 render 函数(每次执行创建新状态)。


十二、设计模式提炼

模式 1:生命周期不对称分离(Lifecycle-Asymmetric Separation)

问题:系统中存在生命周期不同的状态,混在一起会导致重置范围模糊和复用粒度不匹配。

方案:按生命周期分离状态到不同抽象层级——长生命周期的状态放在类中(QueryEngine),短生命周期的状态放在函数中(query() 的 State)。

适用条件

  • 存在明显不同的生命周期(会话级 vs 轮次级)
  • 短生命周期逻辑需要被多种调用方复用
  • 重置边界需要精确控制

模式 2:状态快照传递(State Snapshot Passing)

问题:长生命周期状态持有者需要将状态传给短生命周期逻辑,但不希望后者直接修改前者。

方案:传入状态的浅拷贝快照,短生命周期逻辑在快照上操作,通过 yield 将结果逐条回流,由持有者决定是否合并。

适用条件

  • 需要隔离状态的读写
  • 短生命周期逻辑可能有多种输出(流式消息)
  • 持有者需要选择性合并结果

模式 3:Withheld 错误暂存(Withheld Error Staging)

问题:可恢复错误如果立即暴露给调用方,会导致调用方在恢复期间误终止。

方案:将可恢复错误暂存(不 yield),等恢复结果确定后再决定是否暴露——恢复成功则错误不可见,恢复失败才 surface。

适用条件

  • 错误有明确的恢复路径
  • 调用方可能对中间态错误过度反应
  • 恢复操作在同一循环迭代内完成

模式 4:对称错误恢复(Symmetric Error Recovery)

问题:错误恢复如果用特殊代码路径(如 try/catch + 重试循环),会与正常流程不对称,增加复杂度。

方案:将错误恢复实现为状态机的正常转换——修改 State、设置 transition reason、continue。使恢复路径与正常路径在代码结构上完全对称。

适用条件

  • 使用 while(true) 隐式状态机
  • 错误恢复方向可能因错误类型而异
  • 恢复操作需要修改多个状态字段

模式 5:装饰器式权限包装(Decorator Permission Wrapping)

问题:需要在权限检查函数上添加追踪逻辑,但不希望侵入原函数实现或污染查询循环代码。

方案:在 QueryEngine 层用包装器装饰 canUseTool,将追踪逻辑(如权限拒绝记录)隔离在包装器中,query() 只看到包装后的函数。

适用条件

  • 需要为已有函数添加横切关注点
  • 不希望修改原函数实现
  • 追踪状态需要持久化到会话级

十三、原书对照验证

#原书描述源码验证结论
1“QueryEngine 管理跨轮次状态——消息历史、权限拒绝记录、累计用量、文件缓存”mutableMessagespermissionDenialstotalUsagereadFileState 均为类成员✅ 准确
2“query 管理单轮查询循环……每次用户输入时被创建,输入处理完毕后结束”query()submitMessage()for await 中被调用,每次 submitMessage() 创建新 State✅ 准确
3“query() 的提取是 PR#22546 的重构”ask() 函数作为便利包装器验证了这一演进✅ 准确(源码注释提到 “extracts the core logic from ask()”)
4“query() 接收所有必要的上下文作为参数”QueryParams 类型包含 14 个字段,全部通过参数传入✅ 准确
5“query() 是(几乎)纯函数式的异步生成器”paramsconst 解构不可变,deps 可选注入,但 state.messages 使用 push()✅ 基本准确("几乎"修饰很关键)
6状态所有权矩阵包含 5 个会话级 + 5 个轮次级状态源码验证为 8 个会话级 + 11 个轮次级(含循环局部变量)⚠️ 原书偏少,实际更丰富
7“submitMessage 返回 AsyncGenerator”源码签名:async *submitMessage(...): AsyncGenerator<SDKMessage, void, unknown>✅ 准确
8“query() 返回 AsyncGenerator<QueryYield, Terminal>”源码签名更复杂:联合类型 yield + Terminal return✅ 准确(原书简化了类型)
9“权限包装追踪权限拒绝”wrappedCanUseTool 装饰器,this.permissionDenials.push(...)✅ 准确
10“状态更新采用函数式更新 {…state, …updates}”每个 continue 点都创建新 State 对象✅ 准确
11“state.messages 数组本身是可变的(push() 操作)”assistantMessages.push(message)✅ 准确
12“递归方案有栈溢出风险”while(true) 循环体 1421 行,可能涉及数十轮工具调用✅ 准确(while 避免了栈增长)
13“五层终止保护确保循环最终终止”源码验证为七层(query 层五层 + QueryEngine 层两层)⚠️ 原书偏少,实际多了 USD 预算和结构化输出重试

十四、思考题延伸

原书在 3.13 节提出了 5 个思考题。从源码阅读的角度,对第 5 题做一些延伸:

思考题 5:“二元分离的极限:QueryEngine 和 query() 的分离基于’生命周期不同’的判断。但随着系统演进,可能出现第三种生命周期的状态(比如’跨会话但非永久’的状态)。你会如何设计一个三层状态管理体系?”

源码中已经出现了第三种生命周期的端倪——discoveredSkillNames。它不是纯会话级(每次 submitMessage 清空),也不是纯轮次级(需要在 submitMessage 内的两次 processUserInputContext 重建之间持久)。如果未来出现更多这种"跨轮次但非跨会话"的状态,可能需要一个 TurnContext 中间层:

QueryEngine (会话级)
  └── TurnContext (轮次级,但跨 query() 前后的准备阶段)
       └── query() State (循环迭代级)

但这会增加架构复杂度。Claude Code 当前用 submitMessage() 的局部变量(如 turnCountcurrentMessageUsage)来承载这种中间生命周期状态,是更务实的方案。


小结

二元分离是 Claude Code 引擎层的基石架构决策。它的核心价值不在于"把代码分成两个文件",而在于:

  1. 生命周期对齐:变化频率不同的状态被严格分离,各自有合适的重置策略
  2. 关注点分离QueryEngine 管持久化和跨轮次协调,query() 管单轮循环和错误恢复
  3. 测试隔离query() 的纯函数式设计使循环逻辑可独立测试
  4. 恢复对称性:错误恢复作为状态机的正常转换,与正常流程结构对称
  5. 回流选择性QueryEngine 可以选择性合并 query() 的输出,而非全部接受

原书说"任何长生命周期的交互式系统都能从中受益",这不仅是客套——从编辑器到游戏引擎到交易系统,生命周期不对称分离都是一个值得遵循的原则。


下一篇:按照 SOURCE_CODE_READING_PLAN.md 计划,可继续阶段二的其余部分:3.4 查询生命周期全景、3.5 配置快照模式、3.6 依赖注入、3.7 四层压缩管线。如果您准备继续,请告诉我!

内容概要:本文围绕“基于交流潮流的电力系统多元件N-k故障模型研究”展开,深入探讨了利用Matlab代码实现电力系统在发生多个关键元件同时故障(即N-k故障)情况下的交流潮流计算与故障分析方法。该模型不仅考虑了传统潮流方程的非线性特性,还引入了故障约束条件,能够精确模拟复杂多样的故障场景,如短路、断线等,进而评估电网在极端运行条件下的稳态与动态行为。研究通过构建典型电力系统算例,验证了所提模型在故障筛选、脆弱性识别及系统恢复策略制定方面的有效性,为电力系统安全评估、风险预警和防御体系构建提供了坚实的理论依据和技术支撑。此外,模型具备良好的扩展性,可进一步应用于连锁故障传播分析、恶意攻击模拟等高级安全分析领域。; 适合人群:具备电力系统分析基础理论知识和Matlab编程能力的高校研究生、科研院所研究人员以及电力公司从事电网规划、运行与安全管理的技术人员,特别适用于开展电力系统安全稳定、可靠性评估与应急响应机制研究的专业人士。; 使用场景及目标:①开展电力系统在多重故障条件下的交流潮流仿真,评估系统电压稳定性、线路过载风险及负荷损失程度;②识别电网中的关键薄弱环节与脆弱元件,支撑电网加固改造与防御资源配置;③用于科研项目中的故障场景建模与算法验证,或作为教学案例帮助学生理解复杂故障下的系统响应机制。; 阅读建议:此资源以Matlab代码为核心实现手段,建议读者结合理论推导与代码实现进行对照学习,重点关注故障建模过程中雅可比矩阵的修正方法、故障注入方式及收敛性处理策略,建议在仿真中逐步增加故障数量与复杂度,深入理解N-k故障对系统潮流分布的影响规律,并尝试将其拓展至含新能源接入的现代电力系统场景中进行验证与优化。
【重要提示】本资源设置为0积分下载,若非0积分请勿轻易下载 亲爱的CSDN用户: 首先感谢你点进这个资源页面。我需要提前说明一个重要情况: 本资源原本已设置为“0积分下载”,即作者希望完全免费共享。但CSDN平台有时会根据文件的下载热度、文件大小、用户权限等因素,自动将部分资源的积分调整为非0数值(如1积分、2积分、5积分等)。这是平台系统的自动行为,而非作者本人的设定。 因此,如果你当前看到该资源的下载所需积分不是0(例如显示为1、23……),请谨慎决定是否下载。 如果你按照非0积分支付并下载后发现资源内容不符合预期、链接失效,或者实际上该资源本应是免费的,作者无法为此承担积分损失或退还操作。强烈建议:仅在页面显示为0积分时进行下载。 另外,本资源描述中并未直接提供具体的下载地址或外部链接,因为它本身是一个通过CSDN官方上传通道提交的文件/内容包。如果你看到描述中没有外部网盘地址,这是正常的——资源文件应通过CSDN内置的“下载”按钮获取。若因平台积分显示异常导致你支付了积分,请优先联系CSDN客服咨询积分退还政策,作者没有权限修改平台自动设定的积分值。 感谢你的理解与支持。技术分享本应开放,但受限于平台规则,特此提醒如上。祝学习进步!
内容概要:本文详细介绍了基于PyTorch实现的并行物理信息神经网络(PINNs)在NLS–MB方程孤子演化预测中的应用实例,系统阐述了模型架构设计、损失函数构造、训练流程优化及并行计算策略的实施过程。通过深度融合物理先验知识与深度学习框架,该方法有效求解了非线性薛定谔类偏微分方程,实现了对孤子动力学行为的高精度、高效率数值模拟与长期演化预测,充分展现了PINNs在处理复杂科学计算问题中的强大建模能力与泛化性能。; 适合人群:具备一定深度学习理论基础和偏微分方程求解经验,熟练掌握Python编程语言及PyTorch深度学习框架,从事计算物理、流体力学、光学通信或相关工程仿真的研究生、科研人员及高级技术人员。; 使用场景及目标:①深入理解如何将物理守恒律与控制方程作为硬约束嵌入神经网络,提升模型在稀疏数据下的泛化能力与物理一致性;②掌握PINNs在非线性孤子波、色散介质传播等复杂动力系统建模中的关键技术实现路径;③应用于量子物理、非线性光学、大气海洋动力学等领域中传统数值方法难以求解的高维、强非线性偏微分方程的正/反问题研究。; 阅读建议:建议读者结合文末提供的完整代码资源(可通过公众号“荔枝科研社”获取)进行动手实践,重点关注物理残差项在自动微分框架下的精确计算、多任务损失权重的平衡策略,并尝试迁移模型至其他类型的非线性演化方程以深化理解与应用能力。
内容概要:本文围绕LLC谐振变换器的变频移相混合控制模型展开研究,通过Simulink搭建完整的仿真模型,系统阐述了该控制策略的理论基础与实现方法。研究结合变频控制与移相控制的优点,旨在提升LLC谐振变换器在宽负载范围内的转换效率与系统稳定性,深入分析其在高频高效电源系统中的动态响应特性与优化潜力。文中详细展示了控制逻辑设计、关键参数整定及仿真验证过程,有助于读者全面掌握LLC变换器的工作机理与先进控制技术的应用。; 适合人群:具备电力电子技术、自动控制理论及仿真建模基础的科研人员与工程师,特别适用于从事高频电源、新能源变换系统研发的技术人员,以及电力电子与电气工程方向的研究生及以上学历人员。; 使用场景及目标:①深入理解LLC谐振变换器的核心工作原理及其在轻载与重载工况下的控制挑战;②掌握变频与移相混合控制策略的设计思路、协同机制与仿真建模技巧;③应用于高频DC-DC变换器、电动汽车车载充电机、光伏微逆变器及高效开关电源等高性能电力电子系统的研发与性能优化。; 阅读建议:建议读者结合提供的Simulink仿真模型逐步操作,重点观察系统在不同负载条件下的频率调节与相位调节响应,深入分析效率曲线与谐振腔波形变化,进而掌握控制参数对系统性能的影响规律,可进一步拓展至其他谐振拓扑(如Series Resonant、LCL等)的混合控制策略研究。
内容概要:本文详细介绍了基于物理信息神经网络(PINNs)求解欧拉-伯努利双梁正问题的PyTorch实战方法,通过Python代码实现对双梁结构力学行为的建模与数值求解。该方法将控制偏微分方程作为物理约束嵌入神经网络训练过程中,结合深度学习框架实现无需传统网格划分的高精度数值仿真,适用于复杂工程结构的正问题求解。文中系统阐述了模型架构设计、损失函数构造、边界与初始条件处理、网络训练流程及结果可视化等关键技术环节,突出了PINNs在固体力学领域中融合数据驱动与物理规律的优势。; 适合人群:具备一定深度学习理论基础和力学背景知识,熟悉PyTorch框架使用,从事科学研究或工程技术工作的研究生、高校科研人员及工业界研发工程师。; 使用场景及目标:①掌握物理信息神经网络在结构力学中的建模范式;②实现对欧拉-伯努利梁等经典弹性体问题的无网格神经网络求解;③探索将PINNs拓展至更复杂的多物理场耦合、非线性材料或动态响应分析等问题的新途径;④为工程仿真提供一种避免传统有限元离散化、适应不规则几何和高维问题的替代方案。; 阅读建议:建议读者结合所提供的完整代码逐模块运行与调试,深入理解物理损失项与数据损失项的平衡机制,关注网络超参数选择对收敛性的影响,并尝试修改结构参数、边界条件或外载形式以验证模型泛化能力,进一步推动方法在实际科研项目中的迁移应用。
源码下载地址: https://pan.quark.cn/s/56fcef70b5be **苹果的iTunes历史版本:12.6.5.3** iTunes是由苹果公司开发的一款数字媒体播放软件,它不仅用于维护个人的音乐资料库,还支持与Apple的iPod、iPhone和iPad产品进行同步和交互操作。这个特定的历史版本——12.6.5.3,是在苹果对iTunes实施多次更新和功能优化之后的一个可靠版本。 在12.6.5.3版本中,核心的改进方向在于兼容性提升和稳定性增强。那个时期的iTunes仍然提供了对iOS设备的完整支持,用户可以通过USB数据线将音乐、视频、软件、书籍以及照片等资料传输到他们的iPhone、iPad或iPod touch设备上。同时,它也支持设备的备份和还原功能,以保障用户的数据安全。 在音乐管理领域,iTunes 12.6.5.3展示了一个直观的界面,使用户可以便捷地浏览、播放、整理以及购买音乐。它具备智能播放列表功能,能够依据用户的偏好自动生成播放列表。除此之外,该版本的iTunes融合了Apple Music服务,用户可以付费订阅并获取庞大的在线音乐资源库。 对于视频资料,用户可以欣赏和下载购买的电影及电视剧作品,其中包括高清和4K分辨率的影片。这个版本或许也包含了AirPlay技术的支持,让用户能够将媒体资料无线传输到兼容AirPlay的设备,例如Apple TV。 在设备同步环节,12.6.5.3版的iTunes维持了与各种iOS系统版本的兼容状态,涵盖了当时最新的iOS操作系统。这使用户在将设备升级至最新系统时,依然可以无障碍地管理设备内的内容。 压缩文件包中的`iTunes64Setup.exe`与`iTunes32Setup...
源码下载地址: https://pan.quark.cn/s/a4b39357ea24 依据所提供的文件资料,能够系统性地剖析并归纳出关于HiTool工具操作的相关要点,主要涵盖以下几个领域: ### 一、HiTool工具概述 #### 概述 HiTool是由深圳市海思半导体有限公司研发的一款用于将程序镜像载入到单板Flash中的烧写工具。该工具能够支持多种不同的烧写情境,涵盖一键将所有程序镜像载入到单板Flash、单板已配备BootROM时按地址载入其他程序镜像以及仅载入Boot到单板Flash等操作。 #### 适用产品型号 - **产品名称**:Hi3536 - **产品版本**:V100 #### 目标读者 - **技术支持人员** - **单板软件开发人员** ### 二、环境配置 为了确保HiTool工具能够顺利运行,需要按照以下步骤进行环境准备: 1. **软件配置**:将SDK中的`osdrv\tools\pc_tools\uboot_tools`文件夹内的`HiTool.exe`文件复制到PC的某个本地硬盘中。(PC设备必须安装Windows操作系统) 2. **硬件连接**:保证单板的串口和网线已经正确连接。 3. **工具启动**:运行`HiTool.exe`工具,选择相应的芯片型号(例如Hi3536),然后点击“确定”。 ### 三、分区载入 #### 适用情境 适用于一键将所有程序镜像载入到单板Flash的情况。 #### 载入步骤 1. **启动HiTool工具**:参照“环境配置”的步骤来启动HiTool工具。 2. **选择HiBurn选项**:进入HiBurn烧写工具界面。 3. **选择分区载入模式**:进入分区载入的操作界面...
内容概要:本文系统研究了永磁同步电机(PMSM)调速系统中基于改进滑模、经典滑模及最优滑模控制策略的建模与仿真方法,重点在Simulink环境下构建统一的PMSM调速系统模型,实现三种滑模控制算法的对比分析。研究深入探讨了不同滑模控制在抗干扰能力、动态响应速度与稳态精度等方面的性能差异,剖析了滑模面设计、趋近律选取及抖振抑制等关键技术环节,旨在提升系统鲁棒性与控制品质。文档配套提供了完整的仿真模型与可运行代码,便于读者复现结果并开展进一步优化研究。; 适合人群:具备自动控制原理、电机控制理论基础及Simulink/MATLAB仿真经验的高校研究生、科研人员,以及从事电气传动、新能源汽车、工业自动化等领域技术研发的工程技术人员。; 使用场景及目标:①深入理解滑模控制在永磁同步电机调速系统中的作用机理与工程实现方式;②掌握经典、改进与最优滑模控制器的设计流程与参数整定方法;③通过量化对比不同控制策略的仿真结果,评估其优劣,为实际工程项目中的控制算法选型提供理论依据和技术支持;④服务于科研论文复现、课程设计、学位课题或产品原型开发。; 阅读建议:建议结合所提供的Simulink模型与代码进行动手实践,重点关注控制器模块的搭建逻辑与关键参数设置,通过调整工况条件和扰动输入观察系统响应变化,深入分析抖振现象及其抑制效果,从而全面掌握滑模控制的核心设计思想与应用技巧。
内容概要:本文围绕基于蜣螂优化算法(DBO)的无线传感器网络(WSN)覆盖优化问题展开研究,提出了一种创新且可复现的解决方案。通过Matlab代码实现蜣螂优化算法,针对WSN中传感器节点部署不均导致的覆盖盲区与能耗失衡问题进行建模与优化。研究详细构建了网络覆盖模型与适应度函数,阐述了算法的核心机制与仿真流程,并通过对比实验验证了DBO在提升网络覆盖率、加快收敛速度方面相较于其他智能优化算法的优越性能。该研究不仅提供了完整的算法实现路径,也为复杂工程优化问题提供了有效的智能求解思路。; 适合人群:具备一定Matlab编程基础,从事无线传感器网络、智能优化算法、物联网系统设计及相关领域研究的科研人员、高校研究生及工程技术开发者。; 使用场景及目标:①解决无线传感器网络中节点部署优化问题,最大化监测区域覆盖质量;②为智能优化算法在实际工程中的应用提供可复现的技术案例,推动理论与实践融合;③支持学术论文复现、科研项目验证、课程设计开发及算法性能对比分析。; 阅读建议:建议读者结合所提供的Matlab代码进行仿真实验,深入理解蜣螂优化算法的参数设置、迭代机制与优化过程,掌握其在覆盖优化中的具体实现方式,并可尝试将其迁移应用于路径规划、资源调度等其他组合优化问题中,以拓展算法应用视野。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值