武丢丢

细想全是问题,去做才有答案

MCR 磁控电抗器原理与应用:从直流控制电流到交流等效电感下降的公式推导

1. MCR 是什么

MCR 的英文全称是 Magnetically Controlled Reactor,中文通常称为磁控电抗器。

它可以理解为一种可连续调节电抗值的铁芯电抗器。它的核心思想是:

通过控制直流控制绕组中的直流电流,改变铁芯的磁饱和程度,从而改变交流工作绕组看到的等效磁导率和等效电感,最终改变交流侧吸收的感性无功功率。

简化地说:

直流控制电流增大
→ 铁芯磁场强度增大
→ 铁芯更接近饱和
→ 增量磁导率下降
→ 交流绕组等效电感下降
→ 感抗下降
→ 交流电流增大
→ 吸收的感性无功增大


2. 铁芯线圈的基本电感公式

对于一个简单的铁芯线圈,若磁路近似均匀,电感可以写成:

其中:

  • :线圈电感;
  • :线圈匝数;
  • :铁芯磁导率;
  • :铁芯截面积;
  • :等效磁路长度。

磁导率可以写成:

其中:

  • :真空磁导率;
  • :相对磁导率。

因此:

如果线圈匝数 、铁芯截面积 、磁路长度 不变,那么电感 与磁导率 成正比:

所以:

这就是 MCR 能够通过改变铁芯磁导率来改变电感的基础。


3. 从磁链角度定义电感

线圈的磁链为:

其中:

  • :磁链;
  • :线圈匝数;
  • :每匝线圈所交链的磁通。

电感的定义为:

所以:

这表示:在同样的电流 下,如果线圈能够产生更大的磁通 ,那么它的电感就更大。

但是对于铁芯电抗器来说,铁芯是非线性的。铁芯磁通 与电流 并不总是线性关系。因此更严谨的写法是使用增量电感:

由于:

所以:

这表示交流绕组真正“看到”的电感,是磁链对电流变化的灵敏度。

如果铁芯处于未饱和区,电流变化一点,磁通变化很多,则:

于是:

如果铁芯进入饱和区,电流变化很多,磁通变化很少,则:

于是:

MCR 利用的正是这个原理。


4. 法拉第电磁感应定律与电感公式的关系

你之前提到的 ,来自法拉第电磁感应定律:

其中:

  • :感应电动势;
  • :线圈匝数;
  • :磁通;
  • :磁通随时间的变化率。

负号表示楞次定律,即感应电动势的方向总是阻碍磁通变化。

如果只看大小,可以写成:

而电感电压公式为:

这两个公式本质上描述的是同一件事。

因为电流 产生磁通 ,如果电流变化,则磁通变化,磁通变化又产生感应电压。

即:

如果磁路是线性的,则:

于是可以得到:

但在铁芯饱和时, 不再成严格线性关系,因此电感不再是一个固定常数,而应该用增量电感表示:


5. 直流控制绕组为什么能改变铁芯饱和程度

MCR 中通常可以简化为两类绕组:

  1. 交流工作绕组
    接入交流电网,承担吸收感性无功的作用。

  2. 直流控制绕组
    通入可调直流电流,用来调节铁芯磁饱和程度。

对于绕在铁芯上的线圈,磁场强度近似满足:

其中:

  • :磁场强度;
  • :绕组匝数;
  • :绕组电流;
  • :等效磁路长度。

对于直流控制绕组:

其中:

  • :直流控制绕组匝数;
  • :直流控制电流;
  • :直流控制绕组产生的磁场强度。

因此,当直流控制电流增大时:

则:

也就是说,直流控制电流越大,直流偏置磁场越强。


6. 铁芯饱和程度由什么决定

铁芯的饱和程度主要由铁芯内部的磁感应强度 接近材料饱和值的程度决定。

磁感应强度 与磁场强度 的关系由铁芯材料的磁化曲线决定,也就是 曲线。

在未饱和区:

此时斜率较大:

铁芯等效磁导率较高。

在饱和区:

此时斜率变小:

铁芯等效磁导率下降。

对于交流小信号来说,关键不是静态磁导率,而是增量磁导率:

当铁芯进入饱和区后:

所以 MCR 不是简单地改变某个固定磁导率,而是通过直流偏磁把铁芯工作点推向饱和区,使交流工作点附近的增量磁导率下降。


7. MCR 中总磁场的表达式

MCR 中铁芯受到两个磁场的共同作用:

  1. 直流控制绕组产生的直流磁场
  2. 交流工作绕组产生的交流磁场

因此铁芯中的总磁场可以近似写为:

其中:

交流工作绕组产生的磁场为:

所以:

增大时, 增大,铁芯的工作点整体向 曲线的饱和区移动。

于是交流电流 引起的小范围磁场变化,所对应的磁感应强度变化变小。

即:


8. 从直流控制电流推导到交流等效电感下降

交流绕组的磁链为:

磁通为:

所以:

交流绕组的增量电感定义为:

代入

为常数,则:

利用链式法则:

其中:

又因为:

求导:

因此:

代入电感表达式:

得到:

这就是 MCR 交流工作绕组等效电感的关键公式。

其中:

  • :交流工作绕组匝数;
  • :铁芯在当前工作点附近的增量磁导率;
  • :铁芯截面积;
  • :等效磁路长度。

因为 基本由设备结构决定,一般不变,所以:

而直流控制电流增大时:

所以:

铁芯工作点向饱和区移动:

因此:

最终得到完整推导链条:


9. 等效电感下降后,为什么交流电流会增大

交流电抗为:

其中:

所以:

下降时:

MCR 接在交流母线上,若母线电压有效值近似为 ,则交流电流有效值近似为:

代入

因此:

也就是说,直流控制电流增大后,MCR 的交流侧电抗变小,在相同交流电压下,流过交流绕组的电流增大。


10. 等效电感下降后,为什么吸收的感性无功增大

对于电抗器,其吸收的感性无功功率可以近似写为:

由于电抗器电流近似滞后电压 ,因此其功率主要表现为感性无功。

又因为:

所以:

得到:

代入:

得到:

因此:

完整链条为:

这就是 MCR 通过控制直流电流来调节感性无功吸收量的基本原理。


MCR 的典型应用及公式解释

1. 应用一:吸收无功,调节母线电压

MCR 最典型的应用之一是吸收感性无功,从而调节母线电压。

在电力系统中,线路存在阻抗:

当有功功率 和无功功率 通过线路传输时,线路电压降可以近似表示为:

其中:

  • :线路电压降;
  • :线路传输的有功功率;
  • :线路传输的无功功率;
  • :线路电阻;
  • :线路电抗;
  • :母线电压有效值。

在高压输电系统中,通常:

因此电压降主要受无功功率影响:

这说明:

线

也就是说,调节无功功率就可以调节电压。


2. 为什么 MCR 吸收无功能降低偏高电压

在轻载长线路、电缆线路、新能源场站、电容器投入过多等情况下,系统可能出现容性无功过剩,母线电压偏高。

假设系统中有电容器或线路充电电容提供容性无功 ,MCR 吸收感性无功

那么系统的净容性无功可以近似写为:

当 MCR 增大吸收无功时:

所以:

净容性无功减少,电压支撑减弱,母线电压下降。

因此:

这就是 MCR 吸收无功可以降低偏高电压的原因。

从 MCR 自身控制角度看:

所以,当系统电压偏高时,可以增大 MCR 的直流控制电流,使其吸收更多感性无功,从而降低电压。


3. 为什么 MCR 减少吸收无功能避免电压继续降低

当系统电压偏低时,如果 MCR 仍然大量吸收感性无功,会使系统无功更加紧张,导致电压进一步降低。

此时应该减小 MCR 的吸收无功。

根据:

若希望降低 ,可以增大等效电感

而 MCR 中:

因此电压偏低时,控制系统可以减小直流控制电流,使 MCR 吸收的感性无功减少,从而避免继续拉低电压。


4. 应用二:与电容器组成动态无功补偿装置

MCR 常常不单独使用,而是和固定电容器组、滤波器等组成动态无功补偿装置。

电容器提供容性无功,MCR 吸收可调感性无功。

电容器提供的无功近似为:

其中电容抗为:

因此:

MCR 吸收的感性无功为:

其中:

所以:

若把电容器提供的容性无功记为正,把 MCR 吸收的感性无功记为负,则装置对系统的净无功输出为:

代入:

由于 MCR 的 可以通过直流控制电流连续调节,所以 可以连续调节,进而 也可以连续调节。

即:

这就是 MCR 与电容器配合实现动态无功补偿的基本原理。


5. 应用三:提高功率因数

负荷的视在功率为:

功率因数为:

即:

如果无功功率 过大,则视在功率 增大,功率因数下降。

通过无功补偿,可以减小系统从电网吸收或向电网倒送的净无功功率。

假设负荷消耗感性无功 ,电容器提供容性无功 ,MCR 吸收感性无功 ,则系统净无功可以写为:

补偿后的功率因数为:

如果通过调节 MCR,使 接近目标值,甚至接近 0,则:

于是:

因此,MCR 可以用于配合电容器组进行无功平衡,避免电容器补偿过多或不足,从而提高功率因数。


6. 应用四:抑制电压波动和闪变

一些冲击性负荷会导致无功功率快速变化,例如:

  • 电弧炉;
  • 轧机;
  • 大型电机启动;
  • 大功率冲击性工业负荷。

根据电压降近似公式:

在高压系统中:

所以:

如果负荷无功 快速变化,则电压降也会快速变化:

近似有:

因此,负荷无功波动越大,电压波动越明显。

MCR 可以通过调节自身吸收的无功来抵消一部分负荷无功变化。

假设负荷无功变化为 ,MCR 调节量为 ,则系统看到的净无功变化为:

若控制策略使:

则:

于是:

这说明,通过快速调节 MCR 的无功吸收量,可以减小母线电压波动,抑制电压闪变。


7. 应用五:新能源场站电压与无功控制

风电和光伏场站的输出功率会随风速、光照变化而波动。

新能源并网点的电压常常受到无功功率影响。

可以用电压灵敏度近似表示:

其中:

  • :并网点无功变化;
  • :等效电网电抗;
  • :并网点电压。

当新能源场站发电功率变化、线路充电无功变化或电容补偿状态变化时,母线电压可能升高或降低。

如果并网点电压偏高,可以增大 MCR 吸收无功:

如果并网点电压偏低,可以减小 MCR 吸收无功:

因此,MCR 可以作为新能源场站无功电压控制系统的一部分,用于稳定并网点电压,改善并网电能质量。


8. MCR 控制逻辑总结

MCR 的核心控制对象是直流控制电流

其内部物理过程为:

其中关键公式包括:

直流磁场强度:

增量磁导率:

交流等效电感:

交流感抗:

MCR 吸收的感性无功:

代入

所以:

这就是 MCR 通过直流控制电流调节无功吸收量的本质。


9. 最后总结

MCR 的本质是一种可控铁芯电抗器。

它不是通过机械调节绕组匝数,也不是通过频繁投切电抗器,而是通过直流控制绕组改变铁芯的磁饱和程度。

其关键逻辑为:

  1. 直流控制绕组产生直流偏磁:

  1. 直流控制电流增大,使铁芯工作点向饱和区移动:

  1. 铁芯进入饱和区后,增量磁导率下降:

  1. 交流绕组等效电感下降:

  1. 感抗下降:

  1. 在交流母线电压近似不变时,交流电流增大:

  1. MCR 吸收的感性无功增大:

因此,MCR 可以通过调节直流控制电流,实现对感性无功吸收量的连续调节。

它的主要用途包括:

  • 吸收感性无功,降低偏高母线电压;
  • 与电容器配合,实现动态无功补偿;
  • 提高功率因数;
  • 抑制电压波动和闪变;
  • 用于新能源场站的电压与无功控制;
  • 改善电能质量,提高系统运行稳定性。

一句话总结:

MCR 的核心原理是通过直流偏磁控制铁芯饱和程度,从而改变交流绕组的增量电感,最终实现对交流无功功率的连续调节。

GitHub SSH 连接故障速查(我的踩坑总结)

目标:下次遇到 Permission denied (publickey)git pull/push 失败时,看一眼就能定位 & 修好。
适用:Linux(也适用于 WSL)。
核心原则:GitHub SSH = 公钥在 GitHub,私钥在本机,SSH 必须“用到”那把私钥


0. 典型报错与含义

A) git@github.com: Permission denied (publickey).

  • 含义:SSH 没有用上任何一个 GitHub认可的私钥(最常见)
  • 常见原因:
    1. 私钥文件名不是默认 id_ed25519/id_rsa,SSH 没自动尝试
    2. SSH 尝试了错误的 key(有多把 key)
    3. ssh-agent 没启动/没加载 key(尤其 key 有 passphrase)
    4. key 权限不对,SSH 忽略该 key/config

B) fatal: could not read from remote repository

  • 含义:上面的认证失败导致 Git 无法读远端。

1. 一分钟诊断流程(先跑这些命令)

1.1 看远端用的是 SSH 还是 HTTPS

1
2
3
4
5
6
7
8
9
10
git remote -v
````

* 如果是 `git@github.com:...` → SSH(本文适用)
* 如果是 `https://github.com/...` → 走 HTTPS(需要 PAT/浏览器授权,不用 SSH key)

### 1.2 测试 SSH 到 GitHub 是否能认证

```bash
ssh -T git@github.com
  • ✅ 成功:会显示 Hi <username>! You've successfully authenticated...
  • ❌ 失败:Permission denied (publickey) → 继续下面步骤

1.3 开启详细日志(定位“SSH 到底用哪把 key”)

1
ssh -vT git@github.com

重点看:

  • Offering public key: ...
  • Trying private key: ...
    如果没有出现你期望的私钥文件名,就是 SSH 没用上你那把 key。

2. 正确生成一对新的 SSH 密钥(推荐:自定义名字)

强烈建议:不要拷贝旧电脑私钥,新机器重新生成一对 key 更安全。

2.1 生成 ed25519 key(自定义文件名)

例如命名为 github_cad

1
2
3
4
mkdir -p ~/.ssh
chmod 700 ~/.ssh

ssh-keygen -t ed25519 -C "wu-diu-diu@github" -f ~/.ssh/github_cad

生成:

  • 私钥:~/.ssh/github_cad
  • 公钥:~/.ssh/github_cad.pub

2.2 把公钥添加到 GitHub

1
cat ~/.ssh/github_cad.pub

复制输出 → GitHub:Settings → SSH and GPG keys → New SSH key → 粘贴保存。

2.3 修正权限(SSH 很挑)

1
2
chmod 600 ~/.ssh/github_cad
chmod 644 ~/.ssh/github_cad.pub

3. 让 SSH “只用指定密钥”连接 GitHub(一劳永逸,强烈推荐)

这一步能解决:多 key 混乱、默认 key 找不到、一直 denied 等问题。

编辑 ~/.ssh/config(没有就创建):

1
nano ~/.ssh/config

写入(把文件名改成你的):

1
2
3
4
5
Host github.com
HostName github.com
User git
IdentityFile ~/.ssh/github_cad
IdentitiesOnly yes

权限:

1
chmod 600 ~/.ssh/config

验证:

1
ssh -T git@github.com

4. ssh-agent:什么时候需要?怎么用?(避免每次输入 passphrase)

4.1 什么时候“需要” ssh-agent?

  • 你的私钥设置了 passphrase:不使用 agent 也能用,但会频繁要求输入 passphrase。
  • 你希望一次登录后缓存 key:用 agent 最舒服。

注意:你遇到的 Permission denied (publickey) 多数是没用上正确 key,不一定是缺 agent。

4.2 临时启动 agent 并加载 key(本次会话有效)

1
2
3
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/github_cad
ssh-add -l
  • ssh-add -l 能看到你的 key(如 github_cad)才算加载成功。

4.3(可选)让 agent 更“自动”(systemd 用户)

1
systemctl --user enable --now ssh-agent

~/.bashrc~/.zshrc 增加:

1
export SSH_AUTH_SOCK="$XDG_RUNTIME_DIR/ssh-agent.socket"

然后重新开终端,执行一次:

1
ssh-add ~/.ssh/github_cad

5. 常见问题对照表(看到症状就知道该干啥)

5.1 明明已经把公钥加到 GitHub,还是 denied

✅ 做:

  1. ssh -vT git@github.com 看它到底尝试了哪把 key
  2. ~/.ssh/config 强制 IdentityFile + IdentitiesOnly yes
  3. 检查权限:chmod 700 ~/.ssh && chmod 600 ~/.ssh/config ~/.ssh/<private_key>

5.2 ssh -T git@github.com 成功,但 git pull 失败

可能原因:

  • 认证到的是另一个 GitHub 账号(没仓库权限)
  • 仓库在组织下,可能需要 SSO 授权(网页端提示)
    ✅ 做:
  • 先看 ssh -T 输出的用户名是不是你预期的
  • 确认仓库权限/组织授权

5.3 本机有很多 key,SSH 乱试导致失败

✅ 做:

  • ~/.ssh/configIdentitiesOnly yes
  • 明确指定 IdentityFile

6. Git 操作:只同步远端更新(不用重新克隆)

6.1 正常拉取

1
2
git switch main
git pull

6.2 本地没有要保留的改动,强制对齐远端

1
2
3
git fetch origin
git switch main
git reset --hard origin/main

(可选)清理未跟踪文件(谨慎,会删)

1
git clean -fd

7. 如何检测 SSH “管道/隧道”(端口转发)是否连通(额外常用)

场景:服务器服务只监听 127.0.0.1:8008,外部访问不到,用 SSH 隧道从本地转发。

7.1 在本地开启隧道(本地 8008 → 服务器 127.0.0.1:8008)

1
ssh -N -L 8008:127.0.0.1:8008 user@<server_ip>

7.2 新开终端验证隧道是否通

1
curl -v http://127.0.0.1:8008/

7.3 确认本地 8008 监听者是谁(避免“连到自己本机服务”)

1
lsof -i :8008

看到 ssh 在监听才对。

7.4 本地端口冲突,换端口

1
2
ssh -N -L 18008:127.0.0.1:8008 user@<server_ip>
curl -v http://127.0.0.1:18008/

8. 最终自检清单(从上到下跑一遍就能修)

  1. 远端是 SSH 吗?
1
git remote -v
  1. SSH 能认证 GitHub 吗?
1
ssh -T git@github.com
  1. 失败就看详细日志(看它尝试哪把 key):
1
ssh -vT git@github.com
  1. 强制指定 key(config):
1
2
3
nano ~/.ssh/config
# Host github.com ... IdentityFile ... IdentitiesOnly yes
chmod 600 ~/.ssh/config
  1. 必要时启用 agent 并加载 key:
1
2
3
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/<your_key>
ssh-add -l
  1. 再试:
1
2
ssh -T git@github.com
git pull

9. 常用命令速记

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
# 测试 GitHub SSH 认证
ssh -T git@github.com
ssh -vT git@github.com

# 查看/修复 key & 权限
ls -la ~/.ssh
chmod 700 ~/.ssh
chmod 600 ~/.ssh/config ~/.ssh/<private_key>
chmod 644 ~/.ssh/<public_key>.pub

# agent
eval "$(ssh-agent -s)"
ssh-add ~/.ssh/<private_key>
ssh-add -l

# 远端地址
git remote -v
1
::contentReference[oaicite:0]{index=0}

多智能体框架学习

本文提出一种多智能体,多人格特征框架,旨在通过智能体多样的个性以及批判能力,从而提高模型的推理能力。意思就是让模型扮演不同的人格特征,还要加一个苏格拉底式的critic agent,从而提高整个系统的推理能力。

Intro

介绍了当下的agent系统,要么使用单一agent执行任务,要么仅仅使用两个agent,就算使用多个agent,这多个agent之间多采用相似的推理模式和分析方法,缺少多样性,从而使得整个系统在执行任务的时候缺乏发散性的思维和多角度的探索,而这些能力在解决复杂问题的时候是非常关键的。另外,当下的agent系统缺乏反思和批判的机制,这导致模型缺乏纠错机制,比如如果一开始就理解错了问题,后面就算进行再多的对话和生成,结果都是错误的。

所以,本文提出的multi agent personality shaping 框架就能解决这两个问题。首先整个系统基于什么五人格理论,赋予五个agent不同的人格特性,shape了它们的推理表现,从而提高模型之间合作。另外引入critc模型,负责识别每一个的错误并给出反馈。

框架介绍

分别是interpreter, aligner, scholar, solver, critic

  • 使用mathvita, olympiad, EMMA三种数据集来评测模型解决问题的能力;
  • GPT-4O作为base model

代码

multiagent.py 构建了interpreter, aligner, scholar, solver, cirtic等五类模型,均继承自autogen中的basechatagent类

下面以interpreter为例,介绍该类及其方法。首先介绍basechatagent

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
"""Base class for a chat agent.

This abstract class provides a base implementation for a :class:`ChatAgent`.
To create a new chat agent, subclass this class and implement the
:meth:`on_messages`, :meth:`on_reset`, and :attr:`produced_message_types`.
If streaming is required, also implement the :meth:`on_messages_stream` method.

An agent is considered stateful and maintains its state between calls to
the :meth:`on_messages` or :meth:`on_messages_stream` methods.
The agent should store its state in the
agent instance. The agent should also implement the :meth:`on_reset` method
to reset the agent to its initialization state.

.. note::

The caller should only pass the new messages to the agent on each call
to the :meth:`on_messages` or :meth:`on_messages_stream` method.
Do not pass the entire conversation history to the agent on each call.
This design principle must be followed when creating a new agent.
"""

由以上basechatagent注释可知,创建子类至少需要实现两个方法和一个属性,on_messages方法用来处理传入的消息,on_reset方法用来重置agent的状态,produced_message_types用来定义agent生成的消息类型。

  • agent是有状态的,状态在调用on_messages之后会被保留
  • 每次调用on_messages都只传递新的消息而非整个对话历史

下面来看一下interpreter如何实现on_messages方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
@property  ## 表示interpreter返回的是text类型的信息
def produced_message_types(self) -> Sequence[type[ChatMessage]]:
return [TextMessage]

async def on_messages(self, messages: Sequence[ChatMessage], cancellation_token: CancellationToken) -> Response:
print("InterpreterAgent is called")

if len(messages[0]) == 2: ## 即有初始prompt和PIL Image
if "Improve the description based on the following feedback:" in messages[0][0]:
feedback = messages[0][0]
img = messages[0][1]
prompt = [prompt_interpreter + feedback, img] ## 这里的prompt_interpreter就是给interpreter设置的personality prompt
else:
prompt = [messages[0][0], messages[0][1]]
elif len(messages[0]) > 2:
if "Improve the description based on the following feedback:" in messages[0][0]:
feedback = messages[0][0]
prompt = [prompt_interpreter_m + feedback]
else:
prompt = [prompt_interpreter_m]

for i in range(1, len(messages[0])):
prompt.append(messages[0][i])
else:
response = TextMessage(content = "No image input, Caption is None.", source=self.name)
return Response(chat_message=response)

try: ## 这里是选定哪个厂家的模型作为base model
if SWITCH == 1:
response_text = await self.call_oaLMM(prompt)
elif SWITCH == 0:
response_text = await self.call_LMM(prompt)
elif SWITCH == 2:
response_text = await self.call_QWEN(prompt)

except Exception as e:
print(e)
# print(response_text)
response_message = TextMessage(content=response_text, source=self.name)
return Response(chat_message=response_message)


## 这里他没有定义reset方法
async def on_reset(self, cancellation_token: CancellationToken) -> None:
pass

其余agent的定义和上述interpreter类似,下面介绍一下整个框架的运行逻辑,即五个agent如何合作解决问题。

选择了EMMA作为评测数据集之后,运行run程序就会进入on_messages_e方法。整个逻辑如下:

初始化agent

1
2
3
4
5
6
input_agent = UserProxyAgent("input_agent")
interpreter_agent = InterpreterAgent("interpreter_agent")
aligner_agent = AlignerAgent("aligner_agent")
scholar_agent = ScholarAgent("scholar_agent")
solver_agent = SolverAgent("solver_agent")
critic_agent = CriticAgent("critic_agent")

使用interpreter分析问题, 将interpreter的返回结果输入到execute_pipeline中,这个pipeline中主要逻辑是:aliner->scholar->solver

其中aligner负责分析内容的逻辑一致性,将多个来源的信息整个成一个连贯的描述

scholar负责给问题解构,知识定义,相关性检验,给出问题所属的学科范围,使用的求解范式,相关的公式方法等,类似于一个search的功能

solver顾名思义负责推理,求解。

以上三个agent是主流水线,完成之后,上述四个agent的输出,则会整合,提交给critic,让其对每一个agent的返回结果进行一个评估和审查。cirtic的输出中包含是否需要反馈,最差的步骤,针对最差的步骤的反馈。如果需要反馈,那么针对最差步骤调用的智能体进行改进。

最后将solver的输出结果作为答案存储

总结,五个agent基本上是inter->aligner->scholar->solver的流程,而crtic负责审查每一个agent的输出,如果不够好,那么该agent就需要重新输出,则inter-align-scholar-solve的流程重新进行一遍。

tmux 中的 Session / Window / Panel(Pane)速查指南

前缀键说明:本文基于你的配置,tmux 前缀键为 Ctrl + s


一、三种结构的区别(核心概念)

1
2
3
4
tmux
└─ session → 工作场景
└─ window → 任务
└─ panel(pane) → 并行视图
  • session类比于工作区,不同的工作场景对应不同的session。比如一个跑论文的代码,另一个调试刚clone好的开源项目的代码,是两个不同的项目

  • window是同一个项目下,不同的任务需求。比如调试一个项目的代码。一个window用来查看输出日志,一个window用来监控服务器显卡的显存占用情况

  • panel是需要放在同一个屏幕内分屏同时监控的任务,用于并行查看并操作

二、session相关操作

新建session

1
tmux new -s session_name   ## 在shell中创建一个新的session

关闭session

1
tmux kill-session -t session_name ## 在shell中关闭一个session

Attach session

1
tmux attach -t session_name ## 在shell中attach一个存在的session

Dttach session

1
ctrl + s d (三个键依次按下,这是在tmux的session中,自动detach本session)

三、 window相关操作

新建window

1
Ctrl + s  c

关闭window

1
Ctrl + s  &  (或者更常用的直接输exit

window切换

1
Alt + 1 / 2 / 3 / ...

四、panel相关操作

新建 Panel(分屏)

方向 快捷键
向上分屏 Ctrl + s u
向下分屏 Ctrl + s e
向左分屏 Ctrl + s n
向右分屏 Ctrl + s i

关闭panel

1
exit

panel切换

方向 快捷键
Alt + n
Alt + e
Alt + u
Alt + i

一句话总结

Session 管“场景”,Window 管“任务”,Panel 管“并行”

一、Simulink / Simscape 中的三种端口体系详解

——电气端口、物理信号端口与数值信号端口的本质区别

一句话总结
在 Simulink + Simscape 中,线不是线,端口不是端口
不同端口代表的是不同“物理世界”,混接一定会报错。

本文用于系统性梳理 Simulink 中三类端口的本质区别、连接规则和典型模块,适合作为长期复习用的技术笔记。


如果你只用过纯 Simulink,可能会觉得:

“信号就是一根线,接上就行了。”

但一旦你进入 Simscape(物理建模),Simulink 的世界会被严格划分为 多个“物理域”

  • 电压 ≠ 一个数字
  • 电流 ≠ 一个数字
  • 功率守恒 ≠ 随便算

👉 端口类型的存在,是为了保证物理一致性(Physical Consistency)


二、三种端口的总览对比

端口类型 所属世界 是否守恒 是否有单位 能否进 Scope
电气端口(Electrical Conserving Port) 物理建模
物理信号端口(Physical Signal, PS) 物理信号
数值信号端口(Simulink Signal) 数值计算

三、电气端口(Electrical Conserving Port)

1️⃣ 它是什么?

真实的电气连接端口,代表一个电气节点。

  • 满足 基尔霍夫电流定律
  • 满足 能量守恒
  • 端口本身 不携带“数值信号”

📌 本质:“物理连接点”


2️⃣ 常见模块

  • Resistor(电阻)
  • Capacitor(电容)
  • Inductor(电感)
  • Voltage Source / Current Source
  • Electrical Reference(地)
  • Voltage Sensor / Current Sensor(其 ± 端)

3️⃣ 端口特征

  • 在 MATLAB Engine 里体现为:
    • LConn
    • RConn
  • 不能直接接:
    • Scope
    • Gain
    • Sum
    • PS-Simulink Converter

4️⃣ 示例

Vdc ── R ── C ── GND

这里每一根线都是 电气端口之间的连接


四、物理信号端口(Physical Signal, PS)

1️⃣ 它是什么?

物理量的“数值表达”,但仍然属于 Simscape 世界。

  • 有明确物理意义
  • 有单位(V、A、N、Pa)
  • 不参与守恒计算

📌 本质:“带单位的物理量信号”


2️⃣ 它从哪里来?

通常来自 传感器模块

  • Voltage Sensor → 输出电压(V)
  • Current Sensor → 输出电流(A)
  • Force Sensor / Torque Sensor 等

3️⃣ 常见模块

  • Voltage Sensor(V 端)
  • Current Sensor(I 端)
  • PS-Simulink Converter(输入端)
  • PS Gain / PS Sum(Simscape 内部处理)

4️⃣ 示例

[Voltage Sensor] -> Physical Signal (V)


1️⃣ 它是什么?

普通的 Simulink 数值信号

  • 没有物理单位
  • 只是 double / array
  • 用于控制、算法、显示

📌 本质:“纯数值”


2️⃣ 常见模块

  • Scope
  • Gain
  • Sum
  • PID Controller
  • MATLAB Function

3️⃣ 示例

PS-Simulink Converter ──▶ Scope


六、域转换:三种端口如何“合法交互”

✅ 合法的转换路径(唯一正确方式)

Electrical Domain
↓(Sensor)
Physical Signal (PS)
↓(PS-Simulink Converter)
Simulink Signal


❌ 非法连接(必报错)

错误连接 原因
电气端口 → Scope 域不匹配
电气端口 → PS-SL 缺少传感器
PS 端口 → 电气端口 方向错误
电气端口 → Simulink 模块 物理规则被破坏

七、以 RC 电路测量电容电压为完整示例

正确结构

Vdc ── R ── C ── GND

├─ Voltage Sensor (+)
└─ Voltage Sensor (−)

Voltage Sensor (V)

PS-Simulink Converter

Scope


核心原则

任何物理量想“被看到”,都必须经过:
Sensor → PS → PS-Simulink Converter


如果你在做:

自然语言 → LLM → Simulink 模型

那么模型必须理解:

  • 哪些模块是 物理模块
  • 哪些是 传感器
  • 哪些是 信号处理模块
  • 哪些连接 绝对不能出现

否则就会生成:

❌ “画得出来,但跑不起来”的模型


九、可以写进系统 Prompt 的硬规则

1
2
3
4
5
Rules:
1. Electrical conserving ports can only connect to electrical conserving ports.
2. Physical quantities must be measured using sensors.
3. Physical Signals must be converted using PS-Simulink Converter before entering Simulink.
4. Never connect electrical ports directly to Simulink signals.

二、simulink获取某模块的路径

simulink中某个模块在库浏览器中的路径不是它真实的在simulink引擎中的路径,当我们使用.m代码或者pythpn matlab.engine去添加模块的时候,我们需要知道该模块的simulink引擎中的真实路径

方法一:使用gcb

  • 找到模块所属的上一级库,比如scope所属的上一级是sink库,右键sink库,选择打开sink库,打开后,用鼠标点击scope模块
  • 回到matlab命令行,输入gcb运行,即可看到输出的该模块的路径

方法二、使用get_param

  • 把你想要知道路径的模块拖到sinmulink界面中,用鼠标点击选中它
  • 回到matlab命令行,依次运行h = get_param(gcb, 'Handle')get_param(h, 'ReferenceBlock')
  • 输出的路径就是我们要的路径

知道路径,我们就可以使用add_block将模块添加到模型中,以下是一个RC电路的python代码,使用matlab.engine驱动

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
import matlab.engine

eng = matlab.engine.start_matlab()

model = 'rc_circuit'

eng.new_system(model, 'Model', nargout=0)
eng.open_system(model, nargout=0)

blocks = {
'SolverConfig': 'nesl_utility/Solver Configuration',
'Ground': 'fl_lib/Electrical/Electrical Elements/Electrical Reference',
'Vsrc': 'fl_lib/Electrical/Electrical Sources/DC Voltage Source',
'R': 'fl_lib/Electrical/Electrical Elements/Resistor',
'C': 'fl_lib/Electrical/Electrical Elements/Capacitor',
'PS2SL': 'nesl_utility/PS-Simulink Converter',
'Scope': 'simulink/Sinks/Scope',
'Voltage_Sensor': 'fl_lib/Electrical/Electrical Sensors/Voltage Sensor',
}

for name, path in blocks.items():
eng.add_block(path, f'{model}/{name}', nargout=0)

# 获取端口句柄
ph_vsrc = eng.get_param(f'{model}/Vsrc', 'PortHandles')
ph_r = eng.get_param(f'{model}/R', 'PortHandles')
ph_c = eng.get_param(f'{model}/C', 'PortHandles')
ph_gnd = eng.get_param(f'{model}/Ground', 'PortHandles')
ph_solver = eng.get_param(f'{model}/SolverConfig', 'PortHandles')
ph_ps2sl = eng.get_param(f'{model}/PS2SL', 'PortHandles')
ph_scope = eng.get_param(f'{model}/Scope', 'PortHandles')
ph_vsensor= eng.get_param(f'{model}/Voltage_Sensor', 'PortHandles')


eng.add_line(model, ph_vsrc['LConn'], ph_r['LConn'], 'autorouting', 'on', nargout=0)
eng.add_line(model, ph_r['RConn'], ph_c['LConn'], 'autorouting', 'on', nargout=0)
eng.add_line(model, ph_c['RConn'], ph_gnd['LConn'], 'autorouting', 'on', nargout=0)
eng.add_line(model, ph_gnd['LConn'], ph_solver['RConn'], 'autorouting', 'on', nargout=0)
eng.add_line(model,ph_vsrc['RConn'],ph_gnd['LConn'],'autorouting', 'on',nargout=0)
# Voltage Sensor connections
eng.add_line(model,ph_vsensor['LConn'],ph_c['LConn'],'autorouting', 'on',nargout=0)
eng.add_line(model,ph_vsensor['RConn'][0][1],ph_c['RConn'],'autorouting', 'on',nargout=0)
# Voltage Sensor -> PS2SL
eng.add_line(model,ph_vsensor['RConn'][0][0],ph_ps2sl['LConn'],'autorouting', 'on',nargout=0)
# -> Scope
eng.add_line(model,ph_ps2sl['Outport'],ph_scope['Inport'],'autorouting', 'on',nargout=0)


eng.save_system(model, nargout=0)

腾讯

最长递增子序列

我的解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
from collections import deque

def solution(nums: list[int]):
dq = deque()
dq.append(float('inf'))
for num in nums:
while dq and num <= dq[-1]:
dq.pop()
dq.append(num)
return len(dq)

nums = [4,10,4,3,8,9]
print(solution(nums))
# 3
nums = [4,10,4,3,11,12]
print(solution(nums))
# 输出3 but错误
# 应该输出4

上述方法是构建一个单调递增的栈来试图求解最长递增子序列。但是会遇到一个问题,但num小于等于栈顶元素时,就会将栈顶元素弹出,但其实被弹出的元素可能是更长的递增子序列的一部分。

正确解法

动态规划

1
2
3
4
5
6
7
8
9
10
def solution(nums: list[int]) -> int:
if not nums:
return 0
dp = [1] * len(nums)

for i in range(1, len(nums)):
for j in range(i):
if nums[i] > nums[j]:
dp[i] = max(dp[i], dp[j] + 1)
return max(dp)

动态规划的方法,假设dp中存储数组中对应位置的最长子序列的长度。第一个for循环我们枚举数组中的元素,第二个for循环我们遍历从数组开头到当前枚举元素之间的所有元素,比较二者大小,如果当前元素比其之前的某个元素大,那么更新当前元素的dp[i],取原来的dp[i]和dp[j]+1做比较,如果dp[j]存储的就是到j为止的最长递增子序列的值,那么此时dp[i]>dp[j],dp[i]处的值显然应该更新为dp[j]+1,但是如果在nums[j]之前有更长的递增子序列,也就是说dp[i]很大,那么显然dp[i]不应该更新为dp[j]+1。所以这里应该在dp[i]和dp[j]+1之间取最大值。

贪心 + 二分查找

1
2
3
4
5
6
7
8
9
10
11
import bisect

def solution(nums: list[int]) -> int:
tails = []
for num in nums:
idx = bisect.bisect_left(tails, num)
if idx == len(tails):
tails.append(num)
else:
tails[idx] = num
return len(tails)

这里的bisect是二分查找的一个包。其中besect_left的输入是(list, int),其中list是被查找的有序数组,num是被查找的数。返回的是list中大于等于num的第一个下标索引。而bsect_right返回的是大于num的第一个下标索引。所以如果list中没有num的话,二者返回的都是大于num的第一个元素的下标。如果list中有num,那么besect_left返回的是list中第一个num的下标,bisect_right返回的是list中第一个比num大的元素的下标。

这里使用二分查找去维护一个tails数组,枚举nums中的数,找到num在tail中的为止,最开始tails是空的,那么返回的一定是0,如果返回的索引是tail的长度,即是最后一个索引,那么证明

第一题

拉火车

每个人都有一副牌的一半,即26张,两个人玩拉火车的游戏,即轮流出牌,两个人分别叫alice和bob,轮流出牌构建一个序列,如果某个人出牌A时,前面的序列中有这张牌A1,则这个人将A1-A之间包括A1,A所有牌都收走,下一个人继续出牌。最后直到走完26张牌则游戏结束。结束时候比较谁的牌多即谁胜出。

我的解法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import sys

T = int(sys.stdin.readline().strip())

for _ in range(T):
a = list(map(int, sys.stdin.readline().strip().split()))
b = list(map(int, sys.stdin.readline().strip().split()))
train_list = []
alice_hold_cards = 26
bob_hold_cards = 26
for i in range(len(a)):
# alice 出牌
cur_alice_card = a[i]
if cur_alice_card not in train_list:
train_list.append(cur_alice_card)
else:
index = train_list.index(cur_alice_card)
alice_hold_cards = len(train_list[index:]) + 1
train_list = train_list[:index]

## bob出牌
cur_bob_card = b[i]
if cur_bob_card not in train_list:
train_list.append(cur_bob_card)
else:
index = train_list.index(cur_bob_card)
bob_hold_cards = len(train_list[index:]) + 1
train_list = train_list[:index]
## 如果此次出牌收牌了,则收的时候多加了一个1,这里减掉,数量时对的
## 如果此次出牌没有手牌,则手里的牌数要减1
alice_hold_cards -= 1
bob_hold_cards -= 1

## 每轮结束,判断谁的手牌数量多
if alice_hold_cards > bob_hold_cards:
print("Alice")
elif alice_hold_cards < bob_hold_cards:
print("Bob")
else:
print("Draw")

上述代码有一个可以优化的点时,index()操作是O(n)复杂度,因为列表的元素是按照顺序存储的,index方法会遍历列表,找到对应元素的索引。同样的方法还有x in list, list.remove(), list.count(x)均需要遍历列表。

如果我们将train_list即火车的序列使用字典来保存,键即牌的大小,值即牌的位置,这样既可以判断新的牌是否重复,也可以很快的用键来求得重复牌在火车中的位置,由键-》值这一过程是O(1)复杂度的

优化后代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import sys

T = int(sys.stdin.readline().strip())

for _ in range(T):
a = list(map(int, sys.stdin.readline().strip().split()))
b = list(map(int, sys.stdin.readline().strip().split()))

card_pos = dict()
train = []
alice_cards = 26
bob_cards = 26

for i in range(26):

card = alice_card(i)
if card not in card_pos:
train.append(card)
card_pos[card] = len(train) - 1
alice_cards -= 1
else:
cur_pos = card_pos[card]
alice_cards += len(train[cur_pos:])
for c in train[cur_pos:]
card_pos.pop(c)
train = train[:cur_pos]

# Bob 出牌
card = b[i]
if card not in card_pos:
train.append(card)
card_pos[card] = len(train) - 1
bob_cards -= 1
else:
cur_pos = card_pos[card]
bob_cards += len(train[cur_pos:])
for c in train[cur_pos:]:
card_pos.pop(c)
train = train[:cur_pos]

if alice_cards > bob_cards:
print("Alice")
elif alice_cards < bob_cards:
print("Bob")
else:
print("Draw")

去首都的最短距离(Dij)

AI解法

假如1号城市是首都,一共有n个城市,预先铺好了n-1条道路,还有两个城市之间没有道路,这两个城市之间将要铺一条道路,所有道路的长度都为1m,问,铺完这个将要铺道路的城市之后,所有城市和首都之间的最短距离是多少?

这道题明显要用到Dijkstra算法,首先要了解图这一个数据结构,即由顶点和边构成的一种图结构,边可以代表一定的长度,我们如何表示这个图结构呢?可以使用邻接矩阵的方式,即使用矩阵来表示一个图。假如一个图由四个顶点,如下图所示:

MLP层

使用邻接矩阵来表示即为:

1
2
3
4
5
int[][] graph = new int[][]{
{0 , 2, ∞, 6}
{2 , 0, 3, 2}
{∞ , 3, 0, 2}
{6 , 2, 2, 0}}

(0,0)表示A与A之间的距离,(0,1)表示A与B之间的距离,依次类推。图的边上的数字不仅可以表示为距离,还可以表示为耗时,网络延迟,所有我们统称为权重

如果我们以A为原点,想求出A点到其余所有点之间的距离,该怎么做呢?

这里需要介绍一个堆的概念,堆其实是二叉树的一种,不同的是堆的值是满足一定的规律的。以小顶堆为例,其定义是每一个节点的值都不大于其左右孩子节点的值。大顶堆则相反,每一个节点的值都不小于其左右孩子节点的值。那么这就造成了一个因素,如果我们定义元素出堆只能从堆顶元素出去的话,那么元素出去之后对于小顶堆来说,即构成一个递增序列, 从小到大排列。

  • 建堆:我们可以使用元素一个一个进行建堆如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
a = []
import heapq

heapq.heappush(a, 18)
heapq.heappush(a, 1)

a

[1, 18]

a = [3, 5, 6]

heap.heapify(a)
  • 出堆:从堆顶剔除元素
1
heapq.heappop(a)

dijkstra算法的核心是维护两个结果,一个result是已经求出最小路径的顶点,另一个是还没有求出最小路径的顶点notfound,里面的值都是到终点的距离。

最开始result中只有A点,因为A是起点,所有result = A(0),而notFound={B(2),C(∞),D(6)}.在未求出最小路径的点中,我们选一个距离最小的点B,到达B点后,那么A-B的最小路径已经求得,更新result为:result={A(0),B(2)}, notfound为:notFound={C(∞),D(6)}此时计算B点到其余点的距离,即更新notfound为:notFound={C(5),D(4)},这时我们再找一个路径最短的点D,走到D点,更新result为:result={A(0),B(2),D(4)},此时notfound为:notFound={C(5)},此时我们刷新距离D-》C的距离为2加上D-》A的距离6大于此时C存储的最短距离5,所以不更新。

核心思想是,每次到达一个新的点,我们都要计算其余点与此时点的距离,比较存储的的其余点距离起点的距离,保留更小的那个值。每次要出发下一个点时,要选择距离最短的那个,这样能保证到达该点后所走的路程就是起点距离该点最短的距离。

建图

第一步都是根据问题,建立图结构。比如以上面的图为例,我们可以构建图结构如下:

1
2
3
4
5
6
7
## 1,2,3,4 分别表示A,B,C,D      
graph = {
1: [(2, 2), (4, 6)], # A: B(2), D(6)
2: [(1, 2), (3, 3), (4, 2)], # B: A(2), C(3), D(2)
3: [(2, 3), (4, 2)], # C: B(3), D(2)
4: [(1, 6), (2, 2), (3, 2)] # D: A(6), B(2), C(2)
}

初始化距离

使用一个字典表示每个点和起点的距离。初始默认为无穷大。

1
2
3
4
5
start = 1
## distances中存储所有点与起点的最短距离,所以初始化为无穷大
distances = [node: float('inf') for node in graph]

distances[start] = 0

构建一个优先级队列, 这个队列的优先级是根据相邻节点距离当前节点的距离来排序的,距离越小的越在前面,则越容易先被pop出来

1
priority_queue = [(0, start)]  ## 第一个元素表示当前节点和起点的距离,初始时,起点和起点之间的距离为0
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
while priority_queue:
cur_dist, cur_node = heapq.heappop(priority_queue)

if cur_dist > distances[cur_node]:
continue

for nerbor, weight in graph[cur_node]:
dictance = cur_dist + weight
if dictance < distances[cur_node]:
distances[nerbor] = distance
## 小顶堆构建的时候,如果插入的是元组,则首先比较第一个元素,若相同,再比较第二个元素
## 这里将当前节点的相邻节点插入到小顶堆中时,会根据每个相邻节点计算出来的新的距离来排序,最近的节点会别放在堆顶
heapq.heappush(priority_queue, (distance, nerbor))

return distances

移动路径上的值按位与的最大结果

假设有一个矩阵,我们从左上角移动到右下角,每次只能移动一步,而且只能向右移动和向下移动,路径上的遇到的所有数,都执行与运算,问最大的值是多少?

📌 示例 1

矩阵:
[ [5, 4],
[7, 6] ]

所有可能路径及与运算过程:

  1. 路径:5 → 4 → 6   5 & 4 & 6 = 4
  2. 路径:5 → 7 → 6   5 & 7 & 6 = 4

最大值:4

📌 示例 2

矩阵:
[ [7, 3, 2],
[5, 1, 9],
[6, 8, 4] ]

示例路径一:
路径:7 → 3 → 2 → 9 → 4
运算:7 & 3 & 2 & 9 & 4 = 0

示例路径二:
路径:7 → 5 → 6 → 8 → 4
运算:7 & 5 & 6 & 8 & 4 = 0

最大值:0


我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import sys
import random
T = int(sys.stdin.readline().strip())


tmp = sys.stdin.readline().strip().split()
n, m = int(tmp[0]), int(tmp[1])
matrix = []
move_list = [(1,0), (0,1)]
for _ in range(n):
tmp_list = [int(num) for num in sys.stdin.readline().strip().split(" ")]
matrix.append(tmp_list)
max_result = 0
## 共有多少种可能
## 从左上角到右下角的路径

for _ in range(n * n):
start_pos = [0, 0]
end_pos = [n-1, m-1]
cur_pos = start_pos
last_num = matrix[0][0]
cur_result = 0
while cur_pos != end_pos:
cur_move = random.choice(move_list)
if cur_pos[0] + cur_move[0] >= n:
cur_move = (0, 1)
elif cur_pos[1] + cur_move[1] >= m:
cur_move = (1, 0)
cur_pos[0] = cur_pos[0] + cur_move[0]
cur_pos[1] = cur_pos[1] + cur_move[1]
cur_num = matrix[cur_pos[0]][cur_pos[1]]
cur_result = cur_result & cur_num
last_num = cur_num

if cur_result > max_result:
max_result = cur_result

print(max_result)

第三题 📌 题目描述:矩阵路径最大按位与值(随机模拟法)

给定一个大小为 n × m 的整数矩阵,每次从矩阵的左上角 (0,0) 出发,只能 向右或向下移动一步,直到走到右下角 (n-1,m-1) 为止。

路径上经过的所有元素,使用按位与运算(bitwise AND)进行连续运算,最终得到一个路径结果值。请你通过模拟多条合法路径,找出其中按位与结果最大的那一条路径,并返回其值。

输入格式

  • 第一行一个整数 T,表示测试组数(当前代码中未使用)
  • 第二行两个整数 nm,表示矩阵的行列数
  • 接下来的 n 行,每行包含 m 个整数,表示矩阵的内容

输出格式

  • 输出一个整数,表示模拟过程中所有路径的按位与结果中的最大值

我的代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
import sys
import random
T = int(sys.stdin.readline().strip())


tmp = sys.stdin.readline().strip().split()
n, m = int(tmp[0]), int(tmp[1])
matrix = []
move_list = [(1,0), (0,1)]
for _ in range(n):
tmp_list = [int(num) for num in sys.stdin.readline().strip().split(" ")]
matrix.append(tmp_list)
max_result = 0
## 共有多少种可能
## 从左上角到右下角的路径

for _ in range(n * n):
start_pos = [0, 0]
end_pos = [n-1, m-1]
cur_pos = start_pos
last_num = matrix[0][0]
cur_result = 0
while cur_pos != end_pos:
cur_move = random.choice(move_list)
if cur_pos[0] + cur_move[0] >= n:
cur_move = (0, 1)
elif cur_pos[1] + cur_move[1] >= m:
cur_move = (1, 0)
cur_pos[0] = cur_pos[0] + cur_move[0]
cur_pos[1] = cur_pos[1] + cur_move[1]
cur_num = matrix[cur_pos[0]][cur_pos[1]]
cur_result = cur_result & cur_num
last_num = cur_num

if cur_result > max_result:
max_result = cur_result

print(max_result)

我的代码纯靠概率,每次向下或向右随机选一步,直到到达终点,这就算一次尝试,记录这次尝试的结果。那么多少次尝试就能找到最大值呢,这里我一般设计为矩阵的行数或者列数的次方的最大值。所以是比较靠运气的。提交的时候有一般的测试用例没有通过。

动态规划的方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
import sys
import random
T = int(sys.stdin.readline().strip())

tmp = sys.stdin.readline().strip().split()
n, m = int(tmp[0]), int(tmp[1])
matrix = []

for _ in range(n):
tmp_list = [int(num) for num in sys.stdin.readline().strip().split(" ")]
matrix.append(tmp_list)

dp = [[0] * m for _ in range(n)]
dp[0][0] = matrix[0][0]

for i in range(n):
for j in range(m):
if i == 0 and j == 0:
continue
if i == 0:
## 第0行的每个元素只能是通过向右走走到这里的,所以和左边的上一个元素进行与运算
dp[i][j] = dp[i][j] & dp[i][j - 1]
if j == 0:
## 第0列的每个元素只能是通过向下走走到这里的,所有和上边的上一个元素进行与运算
dp[i][j] = dp[i][j] & dp[i - 1][j]
else:
## 不是第0行和第0列的元素,就找他的上边的元素和左边的元素的与运算的最大值
dp[i][j] = max(dp[i][j] & dp[i - 1][j], dp[i][j] & dp[i][j -1])
## 所以dp数组每个位置的值保存的是当到达当前位置时,进行与运算的结果的最大值,那么我们一直求,直到求出右下角元素的值,就可以直到从左上角到右下角的与运算最大值,同时也知道了从左上角到任何一个点的与运算的结果的最大值

print(dp[n - 1][m - 1])

📝 题目描述

多多最近喜欢玩一款游戏,游戏中共有 n 个关卡,每个关卡各自的难度等级,并且这款游戏很有趣,游戏过程中开出来的关卡难度不是由简单到难,有可能第 i 关难度为 5,而第 i+1 关难度为 2。多多在通关之后想复盘某些关卡精进游戏技巧,好在这款游戏提供了方便的复盘路径,可以指定区间以及最低的难度值 min_d,这段区间中所有难度值大于等于 min_d 的关卡都将被选中。

已知多多选出来的 m 段关卡序列情况,请你推测这款游戏的 n 个关卡至少要划分为多少个不同的难度等级才能满足 m 段关卡都符合上述要求。


🔢 输入描述

  • 第一行一个整数 T,表示共计 T 个测试数据(1 ≤ T ≤ 10)
  • 每组测试数据:
    • 第一行两个整数 nm,分别表示游戏关卡总数和多多选择的复盘序列段数(1 ≤ n, m ≤ 1000)
    • 第二行 n 个整数 a_1, a_2, ..., a_n,表示编号为 1 ~ n 的每个关卡是否被选中,1 表示选中,0 表示没有被选中
    • 接下来 m 行,每行两个整数 [a, b],表示某段关卡区间 [a, b](题目保证 1 ≤ a < b ≤ n

📤 输出描述

每组数据输出一个结果,每个结果占一行,表示最少需要多少种不同的难度等级的划分,才能满足每段 [a, b] 区间中选中的关卡是这段区间中所有大于等于某个 min_d 的所有关卡。


💡 补充说明

  • 对于 20% 的数据:1 ≤ n, m ≤ 10
  • 对于 60% 的数据:1 ≤ n, m ≤ 100
  • 对于 100% 的数据:1 ≤ n, m ≤ 1000

输入:

1
2
3
4
5
1
3 2
1 0 1
1 2
2 3

输出:
2

📎 提示

题目保证每段 [a, b] 中选中的关卡,一定满足存在一个 min_d,使得区间中所有大于等于 min_d 的关卡,正好对应这些被选中的位置。

这题有点难,设计拓扑排序,暂时搁置一下。

包管理器:UV

包管理器就是在python项目中,管理各种依赖的安装和卸载,环境的构建,方便开发者能够不被各种版本依赖困扰。uv是使用rust语言开发的一款包管理器,使用起来非常便捷,最大的感受是安装各种包的时候速度非常的快。

安装

使用pipx安装

  • pipx install uv

使用uv新建一个项目

  • mkdir proj
  • cd proj
  • uv init:创建项目的基本文件
  • uv venv:创建虚拟环境
1
2
3
4
5
6
7
8
9
10
.
├── .venv
│   ├── bin
│   ├── lib
│   └── pyvenv.cfg
├── .python-version
├── README.md
├── main.py
├── pyproject.toml
└── uv.lock

接下来我们可以使用uv pip install 安装包,但是这样安装的包的信息不会立即同步到toml文件和lock文件中,如果使用uv add package命令,则可以让包在安装好后,相关星系能够同步到toml文件和lock文件。

其中toml文件在刚创建好的时候会包含项目的元信息,系统依赖项,构建系统的要去比如:

1
2
3
4
5
6
7
8
9
10
11
[project]
name = "uv-test"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"numpy>=2.3.1",
"pandas>=2.3.0",
"pillow>=11.2.1",
]

其中lock文件是依赖锁文件,记录所有依赖项及其精确版本,确保在不同平台上可复现的安装,这个是uv自动管理的,无需手动编辑:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
[[package]]
name = "numpy"
version = "2.3.1"
source = { registry = "https://pypi.org/simple" }
sdist = { url = "https://files.pythonhosted.org/packages/2e/19/d7c972dfe90a353dbd3efbbe1d14a5951de80c99c9dc1b93cd998d51dc0f/numpy-2.3.1.tar.gz", hash = "sha256:1ec9ae20a4226da374362cca3c62cd753faf2f951440b0e3b98e93c235441d2b", size = 20390372, upload-time = "2025-06-21T12:28:33.469Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/b0/c7/87c64d7ab426156530676000c94784ef55676df2f13b2796f97722464124/numpy-2.3.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6ea9e48336a402551f52cd8f593343699003d2353daa4b72ce8d34f66b722070", size = 21199346, upload-time = "2025-06-21T11:47:47.57Z" },
## 后面还有一大堆

[[package]]
name = "pandas"
version = "2.3.0"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "numpy" },
{ name = "python-dateutil" },
{ name = "pytz" },
{ name = "tzdata" },
]
sdist = { url = "https://files.pythonhosted.org/packages/72/51/48f713c4c728d7c55ef7444ba5ea027c26998d96d1a40953b346438602fc/pandas-2.3.0.tar.gz", hash = "sha256:34600ab34ebf1131a7613a260a61dbe8b62c188ec0ea4c296da7c9a06b004133", size = 4484490, upload-time = "2025-06-05T03:27:54.133Z" }
wheels = [
{ url = "https://files.pythonhosted.org/packages/96/1e/ba313812a699fe37bf62e6194265a4621be11833f5fce46d9eae22acb5d7/pandas-2.3.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8adff9f138fc614347ff33812046787f7d43b3cef7c0f0171b3340cae333f6ca", size = 11551836, upload-time = "2025-06-05T03:26:22.784Z" },
  • source .venv/bin/activate: 激活环境
  • 使用uv add来安装包
  • 安装完使用uv sync来进行同步,这个命令会优先根据lock文件来安装依赖

使用uv从已有项目中构建环境

如果以及有一个项目,这个项目使用toml文件写明了项目需要的依赖,可以使用uv来快速的构建项目

  • 首先创建一个虚拟环境:uv venv,然后激活
  • 运行命令:uv pip install .:uv 会在当前目录下的toml文件,根据它来安装所有的依赖
  • uv lock:可选,生成uv.lock文件

使用uv从requirements.txt中构件环境

如果克隆了一个项目,这个项目使用的是requirements.txt文件作为项目依赖说明,遵循以下步骤可以构件环境:

  • 首先确定运行uv inituv venv,创建好环境,source 激活环境
  • 使用命令uv pip install -r requirements.txt会将requirements中的依赖都安装到环境中,但注意此时是临时安装,因为pyproject.toml文件中并没有显示这些依赖。这里涉及uv的理念,pyproject.toml中写明的依赖是本项目完成后,确实需要的依赖,相当于一个账本将本项目能正常运行所需要的依赖全部记录了下来。但是在开发过程中,我们可能会不断的安装某些依赖,当最后项目完成的时候某些依赖可能是不需要的。所以uv这里并不会自动的将安装到环境中的依赖全部同步到pyproject.toml这个依赖声明中。
  • 如果确定requirement.txt中的全部依赖都是项目需要的依赖,我们需要补记账,此时运行uv add $(cat requirements.txt)或者uv add -r requirements.txt

正确的 uv + requirements.txt 迁移流程(标准范式)

1
2
3
4
5
6
7
8
9
10
11
12
# 1. 初始化 uv 项目
uv init

# 2. 创建虚拟环境
uv venv

# 3. 从 requirements.txt 导入为项目依赖
uv add -r requirements.txt

# 4. 以后只用这一条
uv sync

uv synv、uv add和uv pip install

在uv的开发理念中,项目中有两个东西,一个是项目声明,是一个项目依赖清单,即文件pyproject.toml.另一个是环境状态,即环境中实际装了什么,这个在uv.lock或者venv中可以查看。

  • uv pip install xxx 就是改变环境状态,安装某个包到环境中,但这个包不一定是本项目所必须的,所以该命令不会改变pyproject.toml
  • uv add xxx 手动修改项目声明,该命令添加包之后,这个包就会进入到pyproject.toml中,即认为这个包是项目所必须的,此时uv.lock也会更新
  • uv sync:根据toml文件和uv.lock,强制同步环境,安装toml文件中所有的依赖,删除当前环境中没有声明的包(即不在toml文件中),让环境状态等于项目声明,所以非常适合用在刚把一个项目克隆下来的时候使用,确保环境一致。

总结表格如下:

命令 是否改 pyproject.toml 是否装包 是否更新 uv.lock 定位
uv pip install 临时 / 兼容 pip
uv add 正式依赖管理
uv sync ✅(同步) 环境对齐

从零开始安装docker engine

打开终端,依次运行以下命令:

1
2
## 删除原来安装的旧版本
for pkg in docker.io docker-doc docker-compose docker-compose-v2 podman-docker containerd runc; do sudo apt-get remove $pkg; done
1
2
3
4
5
6
7
8
9
10
11
12
13
## 设置docker的apt软件源
sudo apt-get update
sudo apt-get install ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc

# Add the repository to Apt sources:
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "${UBUNTU_CODENAME:-$VERSION_CODENAME}") stable" | \
sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
1
2
## 安装最新版的docker
sudo apt-get install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
1
2
## 拉取hello-world镜像
sudo docker run hello-world

如果镜像拉取失败,即是国内网络的问题,需要设置镜像源

  • cd /etc/docker/
  • 创建一个json文件:sudo touch daemon.json
  • 编辑文件:sudo vim daemon.json
  • 添加以下内容到文件,保存并退出:
1
2
3
4
5
6
7
8
9
{
"registry-mirrors": [
"https://registry.docker-cn.com",
"https://docker.mirrors.ustc.edu.cn",
"https://hub-mirror.c.163.com",
"https://mirror.baidubce.com",
"https://ccr.ccs.tencentyun.com"
]
}
  • 重新运行daemon和docker: sudo systemctl daemon-reload sudo systemctl restart docker

重新运行sudo docker run hello-world。从输出中可以看出docker的运行逻辑,主要分为以下几个部分:

  • The Docker client contacted the Docker daemon. docker 客户端即用户终端接受命令,比如docker run等,客户端将指令发送给daemon,即服务端
  • The Docker daemon pulled the “hello-world” image from the Docker Hub. docker的服务端即后端负责管理镜像和容器,本地没有hello-world,服务端即会从dockerhub上下载
  • The Docker daemon created a new container from that image which runs the
    executable that produces the output you are currently reading. docer服务端从拉取的镜像中创建了一个实例即容器,容器是一个隔离的环境,可以运行在本地。容器内部执行镜像定义的命令
  • The Docker daemon streamed that output to the Docker client, which sent it
    to your terminal. 容器的输出由守护进程捕获,并通过客户端显示在你的终端上。

一个类比:

Docker 概念 现实类比
镜像(Image) 蛋糕模具
容器(Container) 用模具烤出的蛋糕
Docker Hub 模具商店(可下载模具)
守护进程 蛋糕师傅(执行操作)

Docker Engine、Docker Compose 和 Docker Desktop 的区别

Docker Desktop (GUI + 完整开发环境)
├── Docker Engine (核心运行时)
│ ├── Docker CLI
│ └── Docker Daemon
└── Docker Compose (多容器管理工具)

Docker Engine

核心功能:Docker 的核心运行时环境,包含:,Docker Daemon (dockerd):后台服务,管理容器生命周期,Docker CLI (docker):命令行接口与 Daemon 交互,containerd:容器运行时,实际运行容器,runc:低层容器运行时工具,特点:只提供基础容器功能,纯命令行操作,适合服务器/生产环境使用,需要手动管理网络、存储等

Docker Compose

核心功能:用于定义和运行多容器 Docker 应用的工具,使用 YAML 文件 (docker-compose.yml) 配置应用服务
特点:简化多容器应用管理,一键启动/停止整个应用栈,内置服务依赖管理,集成在 Docker Engine 中 (作为插件),主要用于开发/测试环境

安装docker compose

sudo apt-get install docker-compose-plugin

给docker 配置代理

有的时候国内镜像很慢,或者有的镜像没有,需要走代理访问dockerhub

注意:配置完之后运行以下命令之后,查看出现的代理的ip地址和端口是否正确:

  • sudo systemctl daemon-reload
  • sudo systemctl restart docker
  • docker info | grep -i proxy

配置daemon.json(优先使用这个)

如果docker运行的电脑和你的电脑处于同一个局域网下,而且你的电脑安装了clash可以访问外网,那么即可让docker通过你的代理访问外网,前提是必须打开你电脑上clash的局域网开关

1
2
3
4
5
6
{
"proxies": {
"http-proxy": "http://127.0.0.1:7890",
"https-proxy": "http://127.0.0.1:7890"
}
}

配置环境变量

  • /etc/systemd/system 目录下创建 docker.service.d 目录:sudo mkdir -p /etc/systemd/system/docker.service.d
  • vim /etc/systemd/system/docker.service.d/http-proxy.conf
  • [Service]
    Environment="HTTP_PROXY=http://172.22.11.200:7777"
    Environment="HTTPS_PROXY=http://172.22.11.200:7777"
    Environment="NO_PROXY=localhost,127.0.0.1"
    
    1
    2
    3
    - ```python
    sudo systemctl daemon-reload
    sudo systemctl restart docker

这样就可以使用docker pull从dockerhub拉取镜像了。

注意以上两种方法不能同时配置,不然容易混乱,每个版本的docker究竟优先使用哪个不确定。

使用docker compose运行容器

首先需要新建一个项目,命名为composetest。在composetest中新建一个app.py内容如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import time

import redis
from flask import Flask

app = Flask(__name__)
cache = redis.Redis(host='redis', port=6379)

def get_hit_count():
retries = 5
while True:
try:
return cache.incr('hits')
except redis.exceptions.ConnectionError as exc:
if retries == 0:
raise exc
retries -= 1
time.sleep(0.5)

@app.route('/')
def hello():
count = get_hit_count()
return f'Hello World! I have been seen {count} times.\n'

上述代码简单来说就是用flask搭建web服务,使用redis构建后端服务。flask处理web请求,redis处理用户数据,构建了一个微服务框架。

新建一个compose.yaml:

1
2
3
4
5
6
7
services:
web:
build: .
ports:
- "8000:5000"
redis:
image: "redis:alpine"

配置文件中定义了一个services,其中有两个服务,一个web服务,build:.表示用当前目录下的dockerfile来构建镜像,ports表示将宿主机的本地8000端口映射到容器内部的5000端口。则容器中运行的flask应用会监听5000端口,那么你在宿主机上可以访问:localhost:8000来访问这个服务。但是宿主机一般是远程服务器,如果你的电脑和远程服务器是在同一个局域网下,那么你可以直接在你的电脑中访问:服务器ip:8000来访问这个服务。同时你也可以在你的电脑中运行以下命令:
ssh -L 8888:localhost:8000 用户名@远程服务器IP来将本地的8888端口的访问都转发到远程服务器的8000端口,然后在本地访问:localhost:8888即可。

新建一个requirements.txt:

1
2
flask
redis

新建一个Dockerfile:

1
2
3
4
5
6
7
8
9
10
FROM python:3.10-alpine
WORKDIR /code
ENV FLASK_APP=app.py
ENV FLASK_RUN_HOST=0.0.0.0
RUN apk add --no-cache gcc musl-dev linux-headers
COPY requirements.txt requirements.txt
RUN pip install -r requirements.txt
EXPOSE 5000
COPY . .
CMD ["flask", "run", "--debug"]

之后即可运行docker compose up来运行容器,过程中如果有pull不下来的images,手动使用pull来拉取,好像compose不会自动访问设置好的代理。

docker compose和docker run的区别

docker run

是运行一个容器的基本命令,每次执行都需要在命令行指定所有参数,比如:

1
docker run -d -p 8080:8080 --name searxng searxng/searxng

运行镜像并监听容器的8080端口,将主机的8080端口映射到容器的8080端口,这样就可以通过访问主机的端口来访问容器内提供的服务

另外我们可以将容器的内的某个文件夹挂载到主机的文件夹下,比如:

1
docker run -d --gpus=all -v ollama:/root/.ollama -p 11434:11434 --name ollama ollama/ollama

这里将容器根目录下的.ollama文件夹挂载到了主机的ollama,这个ollama默认位于/var/lib/docker/下,这里ollama被成为卷(volume)存储在主机中,这样就算容器ollama被删除了,但是在容器的.ollama文件夹下下载的模型仍保存在/var/lib/docker/ollama中,这样当我们再次pull一个ollama镜像的时候,只需要重新将.ollama挂载到上述文件夹下即可。

使用了docker run之后,容器就被启动了,在后台运行。把容器看作一个操作系统,我们想访问容器的终端,直接进入容器内操作的话,就需要命令sudo docker exec -it ollama ollama run gemma3:12b,以下是解释:

1. sudo (可选,取决于环境):

  • sudo 命令允许你以超级用户 (root) 权限执行后续命令。
  • 在某些情况下,你可能需要 sudo 才能在 Docker 容器内执行命令,尤其是在容器用户没有足够的权限时。 如果你的 Docker 环境配置良好,你可能不需要 sudo

2. docker exec:

  • docker exec 是 Docker 提供的命令,用于在 已存在的 Docker 容器 中执行命令。 这与 docker run 不同,docker run 是用来创建并启动一个新的容器。
  • exec 允许你访问容器内部的环境,执行命令,就像你直接在容器内操作一样。

3. -it (选项):

  • -i (interactive): 保持 STDIN 打开,允许你向容器发送输入。 这对于交互式使用至关重要,因为你需要和运行的程序(模型)进行交互。
  • -t (tty): 分配一个伪终端。 这使得你可以在容器内看到控制台输出,并使用命令行界面。
  • 结合 -i-t 创建了一个交互式终端会话,允许你与容器内的程序进行交互。

4. ollama (容器名):

  • ollama 是你已经存在的 Docker 容器的名称。 这意味着你之前已经使用 docker run 命令创建并运行了一个名为 ollama 的 Docker 容器。 这个容器通常包含 Ollama 环境,用于管理和运行大型语言模型。 你可以使用 docker ps 命令查看正在运行的容器列表,确认容器名称是否正确。 如果你的 Ollama 容器不是叫 ollama
    你需要替换成正确的容器名称。

5. ollama run gemma3:12b (命令):

  • ollama run 是 Ollama 工具的命令,用于下载和运行大型语言模型。
  • gemma3:12b 是你要运行的模型名称。 gemma3 是模型的名称,12b 指的是模型的大小(120 亿参数)。 Ollama 会自动从 Ollama Hub 下载模型 (如果尚未下载)。

总结:

总而言之,这条命令的执行流程是:

  1. 查找名为 ollama 的已存在 Docker 容器。
  2. ollama 容器内部,执行 ollama run gemma3:12b 命令。 这会下载 Gemma 3.12B 模型(如果尚未下载),并启动模型。
  3. -it 选项使得你能够与运行的模型进行交互,例如输入提示词并接收模型的输出。

所以docker run的特点就是:

  • 一次只能启动一个容器
  • 所有配置通过命令行给出
  • 适合简单的单容器应用
  • 命令可能会很长而且复杂
  • 难以管理多个相关联的容器

docker compose

Docker Compose 是一个用于定义和运行多容器应用的工具,通过 YAML 文件来配置服务:

1
2
3
4
5
6
7
8
9
10
version: '3'
services:
web:
image: nginx
ports:
- "8080:80"
database:
image: mysql
environment:
MYSQL_ROOT_PASSWORD: password

1.. docker compose 的特点:

  • 定义多容器应用: docker compose 专门用于定义和运行多容器的 Docker 应用。
  • 使用 YAML 文件: 它使用 docker-compose.yml (或者 docker-compose.yaml) 文件来描述应用的各个服务(每个服务通常对应一个容器),包括镜像、命令、端口映射、环境变量、依赖关系、网络配置等。
  • 简化多容器管理: 它简化了多容器应用的创建、启动、停止和管理。 你只需要一个命令 docker compose up 就能启动整个应用。
  • 依赖关系管理: docker-compose.yml 文件可以明确定义服务之间的依赖关系,Docker Compose 会按照正确的顺序启动服务。
  • 网络配置: 它可以自动创建和配置服务之间的网络,方便服务间的通信。
  • 可重复性: docker-compose.yml 文件可以作为应用的配置清单,方便在不同的环境中使用相同的配置。
  • 编排功能: docker compose 提供了一些编排功能,例如健康检查、自动重启等。
  • 可扩展性: 可以更容易地扩展应用,例如增加服务实例或修改网络配置。

docker 常用命令

1. 镜像 (Images)

  • docker pull <image_name>[:<tag>]: 从 Docker Hub 或其他注册中心下载镜像。
    • 例如:docker pull ubuntu:latest (拉取最新版本的 Ubuntu 镜像)
  • docker images: 列出本地仓库中的镜像。
  • docker rmi <image_id> [<image_id> ...]: 删除本地镜像。 需要指定镜像 ID 或名称 (如果唯一)。
    • docker rmi -f <image_id>: 强制删除镜像,即使它被其他镜像使用。
  • docker build -t <image_name>:<tag> <path_to_dockerfile>: 从 Dockerfile 构建镜像。
    • 例如: docker build -t my-app:1.0 . (在当前目录构建一个名为 my-app,标签为 1.0 的镜像)
  • docker tag <source_image>:<source_tag> <target_image>:<target_tag>: 给镜像添加别名 (用于更方便的推送和管理
    )。

2. 容器 (Containers)

  • docker run [OPTIONS] <image_name> [COMMAND] [ARG...]: 创建并运行容器。 这是 Docker 最常用的命令之一。
    • docker run -d <image_name>: 在后台运行容器 (detached mode)。
    • docker run -p <host_port>:<container_port> <image_name>: 将容器的端口映射到主机端口。
    • docker run -v <host_path>:<container_path> <image_name>: 将主机目录挂载到容器目录 (用于数据持久化)。
    • docker run --name <container_name> <image_name>: 给容器指定一个名称。
    • docker run -e <key>=<value> <image_name>: 设置环境变量
  • docker ps [OPTIONS]: 列出正在运行的容器。
    • docker ps -a: 列出所有容器 (包括已停止的)。
    • docker ps -q: 只显示容器 ID。
  • docker start <container_id_or_name>: 启动已停止的容器。
  • docker stop <container_id_or_name>: 停止正在运行的容器。
  • docker restart <container_id_or_name>: 重启容器。
  • docker kill <container_id_or_name>: 强制停止容器 (更激进的停止方式)。
  • docker rm <container_id_or_name> [container_id_or_name...]: 删除一个或多个容器。
    • docker rm -f <container_id_or_name>: 强制删除运行中的容器。
  • docker exec -it <container_id_or_name> <command>: 在正在运行的容器中执行命令。
    • 例如: docker exec -it my_container bash (在 my_container 容器中打开一个 bash shell)
  • docker top <container_id_or_name>: 查看容器内的进程信息。
  • docker logs <container_id_or_name>: 查看容器的日志。
    • docker logs -f <container_id_or_name>: 实时查看容器日志。
  • docker inspect <container_id_or_name>: 显示容器的详细信息 (JSON 格式)。

3. 网络 (Networks)

  • docker network ls: 列出 Docker 网络。
  • docker network create <network_name>: 创建一个 Docker 网络。
  • docker network connect <network_name> <container_id_or_name>: 将容器连接到 Docker 网络。
  • docker network disconnect <network_name> <container_id_or_name>: 将容器从 Docker 网络中分离。
  • docker network inspect <network_name>: 查看 Docker 网络的详细信息。

4. 卷 (Volumes)

  • docker volume ls: 列出 Docker 卷。
  • docker volume create <volume_name>: 创建 Docker 卷。
  • docker volume inspect <volume_name>: 查看 Docker 卷的详细信息。
  • docker volume rm <volume_name>: 删除 Docker 卷。

5. Docker Compose (用于多容器应用)

  • docker-compose up [OPTIONS]: 构建、创建和启动 Docker Compose 应用。
    • docker-compose up -d: 在后台运行应用。
  • docker-compose down [OPTIONS]: 停止并删除 Docker Compose 应用。
  • docker-compose ps: 列出 Docker Compose 应用的容器状态。
  • docker-compose logs: 查看 Docker Compose 应用的日志。
  • docker-compose build: 构建 Docker Compose 应用的镜像。
  • docker-compose exec <service_name> <command>: 在 Compose 服务中执行命令。

6. 其他常用命令

  • docker info: 显示 Docker 环境的信息。
  • docker version: 显示 Docker 的版本信息。
  • docker system df: 显示 Docker 的资源使用情况 (磁盘空间)。
  • docker system prune: 清理无用的 Docker 对象 (容器、镜像、卷、网络)。 这是一个非常有用的命令,可以释放磁盘空
    间。
    • docker system prune -a: 删除所有未被使用的镜像、容器、卷和网络。谨慎使用!

提示:

  • 可以使用 docker --help 查看所有可用命令的帮助信息。
  • 可以使用 docker <command> --help 查看特定命令的帮助信息。
  • docker 命令通常需要使用 sudo 在 Linux 系统上运行,但在 Docker Desktop 上可能不需要。

参考文献

一文讲透如何给Docker设置代理

Docker 设置代理的三种方法

docker官方文档

0%