1297.12 명확한 참/거짓 구분 원칙
1. 원칙의 정의
명확한 참/거짓 구분 원칙은 조건 노드의 평가 결과가 SUCCESS(참) 또는 FAILURE(거짓)로 모호함 없이 이분되어야 한다는 설계 원칙이다. 조건 노드가 평가하는 명제는 현재 상태에서 반드시 참이거나 거짓이어야 하며, “불확실”, “부분적 충족”, “대략적 만족“과 같은 중간 상태를 허용하지 않는다(Colledanchise & Ogren, 2018).
행동 트리(Behavior Tree)의 제어 노드는 자식 노드의 반환 상태에 기반하여 결정론적으로 실행 흐름을 제어한다. 조건 노드의 결과가 모호하면 제어 노드의 분기 결정이 비예측적이 되며, 이는 행동 트리 전체의 동작 신뢰성을 저하시킨다.
2. 이진 판정의 필요성
2.1 제어 노드의 결정론적 실행
Sequence 노드는 자식이 SUCCESS이면 다음 자식으로 진행하고, FAILURE이면 즉시 중단한다. Fallback 노드는 자식이 FAILURE이면 다음 자식으로 진행하고, SUCCESS이면 즉시 반환한다. 이러한 이진 분기 논리는 조건 노드의 결과가 명확히 두 가지 중 하나일 때만 정확하게 동작한다.
2.2 트리 구조의 해석 가능성
행동 트리의 장점 중 하나는 트리 구조를 읽는 것만으로 로봇의 의사 결정 로직을 이해할 수 있다는 것이다. 조건 노드의 참/거짓 기준이 명확하면, 트리의 각 분기가 어떤 상황에서 선택되는지를 직관적으로 파악할 수 있다.
3. 모호한 판정이 발생하는 상황
3.1 연속 값의 이산화
센서 데이터는 연속 값(continuous value)으로 측정되는 경우가 대부분이다. 배터리 잔량, 거리, 온도, 속도 등은 실수 범위의 값을 가지며, 이를 참/거짓으로 변환하려면 임계값(threshold)을 설정해야 한다. 임계값의 정의가 불명확하면 경계 부근에서 판정이 모호해진다.
3.2 임계값 근처의 진동
물리적 센서 값은 잡음(noise)을 포함하므로, 임계값 근처에서 조건 노드의 결과가 tick마다 SUCCESS와 FAILURE 사이를 진동(oscillation)할 수 있다. 이 현상은 채터링(chattering)이라 하며, 행동 트리의 실행 흐름을 불안정하게 만든다.
tick 1: battery = 20.1% → SUCCESS (임계값: 20%)
tick 2: battery = 19.9% → FAILURE
tick 3: battery = 20.0% → ? (경계값)
tick 4: battery = 20.1% → SUCCESS
3.3 데이터 부재 또는 비정상 값
블랙보드 키가 존재하지 않거나, 센서 값이 NaN(Not a Number) 또는 Inf(Infinity)인 경우, 조건의 참/거짓을 결정할 수 없는 상황이 발생한다.
4. 명확한 판정 기준의 설정
4.1 비교 연산자의 명시적 정의
임계값 비교에서 사용하는 비교 연산자를 설계 시점에 명확히 정의하고 일관되게 적용해야 한다.
| 비교 유형 | 연산자 | SUCCESS 조건 | 예시 |
|---|---|---|---|
| 이상 | \geq | v \geq t | 배터리 \geq 20% |
| 초과 | > | v > t | 거리 > 1.0m |
| 이하 | \leq | v \leq t | 속도 \leq 2.0m/s |
| 미만 | < | v < t | 온도 < 80°C |
비교 연산자의 선택은 도메인의 요구사항에 따라 결정되며, 선택된 연산자를 노드의 문서화와 명명에 반영한다.
4.2 히스테리시스를 통한 채터링 방지
임계값 근처에서의 채터링을 방지하기 위해 히스테리시스(hysteresis)를 적용할 수 있다. 히스테리시스는 상승 임계값(rising threshold)과 하강 임계값(falling threshold)을 분리하여, 상태 전이에 일정한 여유(margin)를 부여하는 기법이다.
BT::NodeStatus IsBatteryOk::tick()
{
double battery;
getInput("battery", battery);
double high_threshold; // 상승 임계값 (예: 22%)
getInput("high_threshold", high_threshold);
double low_threshold; // 하강 임계값 (예: 18%)
getInput("low_threshold", low_threshold);
if (last_result_ == BT::NodeStatus::SUCCESS)
{
// 이전에 SUCCESS였으면, 하강 임계값 이하로 내려가야 FAILURE
return (battery >= low_threshold)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
}
else
{
// 이전에 FAILURE였으면, 상승 임계값 이상으로 올라가야 SUCCESS
return (battery >= high_threshold)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
}
}
히스테리시스를 적용하면, 배터리가 정확히 20% 근처에서 진동하더라도 조건 결과가 안정적으로 유지된다. 상승 임계값(22%)을 넘어야 SUCCESS로 전환되고, 하강 임계값(18%) 아래로 내려가야 FAILURE로 전환된다.
4.3 부동소수점 비교의 처리
부동소수점(floating-point) 연산에서 등호 비교(==)는 수치적 오차로 인해 예기치 않은 결과를 산출할 수 있다. 부동소수점 값의 등호 비교에는 허용 오차(tolerance, epsilon)를 적용해야 한다.
BT::NodeStatus IsAtTargetAngle::tick()
{
double current_angle, target_angle, epsilon;
getInput("current_angle", current_angle);
getInput("target_angle", target_angle);
getInput("epsilon", epsilon);
// 부동소수점 근사 비교
return (std::abs(current_angle - target_angle) <= epsilon)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
}
5. 비정상 입력에 대한 판정 규칙
5.1 데이터 부재
블랙보드에서 필요한 키를 읽을 수 없는 경우, 실패 안전(fail-safe) 원칙에 따라 FAILURE를 반환한다.
BT::NodeStatus IsDistanceSafe::tick()
{
auto result = getInput<double>("distance");
if (!result)
{
return BT::NodeStatus::FAILURE; // 데이터 부재: 안전하지 않음으로 판정
}
double distance = result.value();
double threshold;
getInput("threshold", threshold);
return (distance > threshold) ? BT::NodeStatus::SUCCESS : BT::NodeStatus::FAILURE;
}
5.2 NaN 및 Inf 처리
IEEE 754 부동소수점 표준에서 NaN은 어떤 비교 연산에서도 참을 반환하지 않는 특수한 값이다. NaN과의 비교는 항상 거짓이므로, 별도의 검사 없이도 FAILURE가 반환될 수 있다. 그러나 명시적으로 검사하여 의도를 명확히 하는 것이 바람직하다.
BT::NodeStatus IsValueValid::tick()
{
double value;
getInput("value", value);
// NaN, Inf 명시적 검사
if (std::isnan(value) || std::isinf(value))
{
return BT::NodeStatus::FAILURE;
}
double threshold;
getInput("threshold", threshold);
return (value >= threshold) ? BT::NodeStatus::SUCCESS : BT::NodeStatus::FAILURE;
}
6. 다중 상태의 이진 변환
로봇 시스템에서 일부 상태는 본질적으로 다중 값(multi-valued)을 가진다. 비행 모드(MANUAL, STABILIZED, OFFBOARD 등), 시스템 상태(INITIALIZING, READY, ERROR 등)가 그 예이다. 이러한 다중 상태를 조건 노드에서 이진 판정으로 변환할 때, 판정 기준을 명확히 정의해야 한다.
// 특정 모드인지 확인하는 조건 노드
BT::NodeStatus IsFlightMode::tick()
{
std::string current_mode;
getInput("current_mode", current_mode);
std::string expected_mode;
getInput("expected_mode", expected_mode);
return (current_mode == expected_mode)
? BT::NodeStatus::SUCCESS
: BT::NodeStatus::FAILURE;
}
이 설계에서는 “현재 모드가 기대 모드와 일치하는가?“라는 명확한 이진 명제를 평가한다.
7. 판정 기준의 문서화
조건 노드의 판정 기준은 코드 내에 명확히 문서화되어야 한다. 특히 다음 사항을 명시한다:
SUCCESS가 의미하는 상태FAILURE가 의미하는 상태- 사용되는 비교 연산자 (이상/초과/이하/미만)
- 경계값에서의 판정 결과
- 데이터 부재 시의 기본 판정
이러한 문서화는 트리 설계자가 조건 노드를 올바르게 배치하고, 디버깅 시 결과를 정확히 해석하는 데 필수적이다.
8. 참고 문헌
- Colledanchise, M., & Ogren, P. (2018). Behavior Trees in Robotics and AI: An Introduction. CRC Press.
- Faconti, D., & Colledanchise, M. (2022). BehaviorTree.CPP Documentation. https://www.behaviortree.dev/
version: 0.1.0