PyDrake状态向量使用关系运算符触发RuntimeError的解决咨询
问题背景
我在为植物模型编写DoCalcTimeDerivatives方法时,通过大量条件语句定义导数,但构建Diagram时触发如下RuntimeError:
RuntimeError: You should not call
__bool__/__nonzero__onFormula. If you are trying to make a map withVariable,Expression, orPolynomialas keys (and then access the map in Python), please use pydrake.common.containers.EqualToDict`.
尝试用pydrake提供的lt函数替代原生比较运算符后,仍出现相同错误。
复现代码
from pydrake.all import ( DiagramBuilder, LeafSystem_, SceneGraph, namedview, lt, ) TestState = namedview( "TestState", ["x", "xdot"] ) @TemplateSystem.define("TestPlant_") def TestPlant_(T): class Impl(LeafSystem_[T]): def _construct(self, converter=None): LeafSystem_[T].__init__(self, converter) self.DeclareVectorInputPort("force", 1) self.DeclareContinuousState(2) self.DeclareVectorOutputPort("state", 2, self.CopyStateOut) def _construct_copy(self, other, converter=None): Impl._construct(self, converter=converter) def DoCalcTimeDerivatives(self, context, derivatives): s = TestState( context.get_mutable_continuous_state_vector().CopyToVector() ) force = self.EvalVectorInput(context, 0)[0] sdot = TestState(s[:]) sdot[0] = s[1] if (s[0] < 0): sdot[1] = force else: sdot[1] = 2*force derivatives.get_mutable_vector().SetFromVector(sdot[:]) def CopyStateOut(self, context, output): x = context.get_continuous_state_vector().CopyToVector() output.SetFromVector(x) return Impl TestPlant = TestPlant_[None] builder = DiagramBuilder() glider = builder.AddSystem(TestPlant()) scene_graph = builder.AddSystem(SceneGraph()) diagram = builder.Build()
报错详情
--------------------------------------------------------------------------- RuntimeError Traceback (most recent call last) Cell In[18], line 49 47 glider = builder.AddSystem(TestPlant()) 48 scene_graph = builder.AddSystem(SceneGraph()) ---> 49 diagram = builder.Build() Cell In[18], line 33, in TestPlant_..Impl.DoCalcTimeDerivatives(self, context, derivatives) 31 sdot = TestState(s[:]) 32 sdot[0] = s[1] ---> 33 if (s[0] < 0): 34 sdot[1] = force 35 else: RuntimeError: You should not call `__bool__` / `__nonzero__` on `Formula`. If you are trying to make a map with `Variable`, `Expression`, or `Polynomial` as keys (and then access the map in Python), please use pydrake.common.containers.EqualToDict`.
已尝试的方案
从官方文档看到说明:
As a workaround, this module provides the following vectorized operators, following suit with the operator builtin module: lt, le, eq, ne, ge, and gt.
于是将判断语句改为:
if (lt(s[0],0)): # instead of if (s[0] < 0):
但仍触发相同错误:
--------------------------------------------------------------------------- RuntimeError Traceback (most recent call last) Cell In[19], line 49 47 glider = builder.AddSystem(TestPlant()) 48 scene_graph = builder.AddSystem(SceneGraph()) ---> 49 diagram = builder.Build() Cell In[19], line 33, in TestPlant_..Impl.DoCalcTimeDerivatives(self, context, derivatives) 31 sdot = TestState(s[:]) 32 sdot[0] = s[1] ---> 33 if (lt(s[0],0)): 34 sdot[1] = force 35 else: RuntimeError: You should not call `__bool__` / `__nonzero__` on `Formula`. If you are trying to make a map with `Variable`, `Expression`, or `Polynomial` as keys (and then access the map in Python), please use pydrake.common.containers.EqualToDict`.
解决方案
问题核心:不能用Python原生if语句判断Drake的Expression/Formula对象。当系统模板参数为None(符号模式)时,s[0]是Expression类型,lt(s[0],0)返回Formula对象,Python的if会尝试调用其__bool__方法,这在Drake中是被禁止的。
正确做法是用分段表达式替代Python条件分支,使用Drake提供的if_then_else函数构建符号化的条件逻辑:
修改后的DoCalcTimeDerivatives方法:
from pydrake.all import if_then_else # ... 其他代码保持不变 ... def DoCalcTimeDerivatives(self, context, derivatives): s = TestState( context.get_mutable_continuous_state_vector().CopyToVector() ) force = self.EvalVectorInput(context, 0)[0] sdot = TestState(s[:]) sdot[0] = s[1] # 用if_then_else构建分段表达式,替代Python原生if分支 sdot[1] = if_then_else(s[0] < 0, force, 2*force) derivatives.get_mutable_vector().SetFromVector(sdot[:])
原理说明
if_then_else(condition, true_val, false_val)会生成一个符号表达式,在符号模式下完整保留条件逻辑,不会触发__bool__调用。- 系统运行在数值模式时,该表达式会自动计算对应分支的值,行为与原生条件分支一致。
- 复杂多分支场景可嵌套使用
if_then_else,或通过PiecewiseFunction类管理。
内容的提问来源于stack exchange,提问作者Jay

