Found an issue with the book? Report it on Github.

상태 이벤트 핸들링(State Event Handling)

상태 이벤트 핸들링(State Event Handling)

이미 시간 이벤트상태 이벤트 를 모두 소개하면서, 상태 이벤트와 관련된 몇 가지 중요한 까다로운 문제를 살펴 보았습니다.놀랍게도 이러한 까다로운 문제들은 간단한 모델에서도 발생할 수 있습니다.

기본 부식 모델(Basic Decay Model)

다음과 같은 매우 자명한(trivial) 모델을 살펴 보겠습니다.

model Decay1
  Real x;
initial equation
  x = 1;
equation
  der(x) = -sqrt(x);
end Decay1;

이 모델을 5초 동안 시뮬레이션하려고 하면, 다음 궤적으로 계산하다가 약 2초 후에 시뮬레이션이 종료됨을 알 수 있습니다.

/static/_images/Decay1.svg

다시 말하지만, 수치적인 문제(numerical issues)가 조금씩 발생합니다. 수학적으로 x 값이 0 아래로 떨어지는 것이 가능하지 않은 이와 같은 경우에도, 수치 적분 기술을 사용하면 약간의 오류가 발생할 수 있으며 이때 0 이하로 x 를 연산합니다. 이 경우 sqrt(x) 표현식이 부동 소수점 예외를 생성하고, 결국 시뮬레이션은 종료 됩니다.

가드 표현(Guard Expressions)

다음과 같이 음수의 제곱근을 연산하지 않도록 보호하기 위해 if 표현식을 도입할 수 있습니다.

model Decay2
  Real x;
initial equation
  x = 1;
equation
  der(x) = if x>=0 then -sqrt(x) else 0;
end Decay2;

이 모델을 시뮬레이션하면 다음 궤적을 얻습니다 [1]

/static/_images/Decay2.svg

다시 또 시뮬레이션은 실패합니다.하지만 왜 그런것 일까요? 결국 동일한 이유로 실패 하는 것 입니다. 음수의 제곱근을 취한 결과 발생하는 수치적 예외 가 발생한 것 입니다.

대부분의 사람들은 가드 식을 도입한 후 이와 같은 부동 소수점 예외(또는, 예를 들어 0으로 나누기)에 대한 오류 메시지를 볼 때 매우 당황합니다. 그들은 자연스럽게 x 가 0보다 작은 경우에는, sqrt(x) 를 연산하는 경우가 없다고 가정합니다. 하지만 이 가정은 틀렸습니다.

이벤트와 조건 표현(Events and Conditional Expressions)

동작에서의 이벤트의 역할(The Role of Events in Behavior)

주어진 if 표현은 아래와 같습니다.

der(x) = if x>=0 then sqrt(x) else 0;

이 경우 sqrt 가 음수 인수로 호출될 가능성이 있습니다. 그 이유는 이것이 상태 이벤트라는 사실과 관련이 있습니다. 시간 이벤트 의 경우에는 발생하는 시간을 미리 알고 있습니다. 그러나, 이 예시와 같은 상태 이벤트의 경우 해당되지 않습니다. 이벤트가 언제 발생할지 결정하려면, 조건 ( x>=0 이 false가 됨)을 확인하기 위해 해의 궤적을 검색해야 합니다.

여기서 이해해야 할 중요한 사항은, 이벤트가 발생할 때까지 동작이 바뀌지는 않는다는 것입니다.이 if 표현식의 양변은 der(x)=sqrt(x)der(x)=0 의 두 가지 동작 유형을 가지고 있고,``x`` 는 처음에 0보다 크므로 초기 동작은 der(x)=sqrt(x) 입니다. 솔버는 ``x>=0`` 으로 표시되는 이벤트 의 시간을 결정할 때까지 이 방정식을 계속 사용합니다. 즉, 그 이벤트의 시간을 결정하기 위해서는 **조건식의 값이 변하는 시점을 넘어야 합니다**결국 이말은 x>=0 조건이 참에서 거짓으로 변하는 시점을 정확하게 결정하려고 시도하는 동안 ``x `` 는 음수라는 의미 입니다.

대부분의 사용자는 처음에 der(x) 가 연산될 때, if 표현식(특히 if 표현식의 조건식)이 해석된다고 생각합니다. 이전 단락에서 이것이 사실이 아님을 분명히 이해 했기를 바랍니다.

적분을 시도하고 재시도하는 데 소요되는 이 시간은 모델리카가 if 표현식에서 소위 "제로 크로싱(zero crossing)" 함수를 추출할 수 있다는 사실 덕분에 절약할 수 있습니다. 이 함수는 일반적으로 이벤트가 발생할 지점에 근(root)을 갖도록 구성되기 때문에 제로 크로싱 함수라고 합니다.예를 들어 다음과 같은 if 표현식이 있는 경우

y = if a>b then 1 else 0;

제로 크로싱 함수는 a-b 입니다. a>b 지점에서 정확히 양수에서 음수로 변하기 때문에 이 함수를 선택 합니다.

이전 방정식을 다시 생각해 보겠습니다.

der(x) = if x>=0 then sqrt(x) else 0;

이 경우 제로 크로싱 함수는 단순히 x 자체가 0을 교차할 때 이벤트가 발생하기 때문에 x 입니다.

모델리카 컴파일러는 적분기가 사용할 모델의 모든 제로 크로싱 함수를 수집합니다. 적분하는 동안 적분기는 제로 크로싱 함수 중 부호가 변경된 것이 존재하는지 확인합니다.이런 함수가 있는 경우, 해당 단계에서 계산한 해을 사용하여 제로 크로싱 함수를 보간하여 제로 크로싱 함수 근의 위치를 시간적으로 찾게 되며, 이때 찾은것이 이벤트가 발생하는 시점입니다.이 프로세스는 근을 찾는 알고리즘이 근의 위치를 식별하는 데 도움이 되는 더 많은정보(제로 크로싱 함수의 도함수와 같은 정보)를 가지고 있기 때문에 훨씬 더 효율적이며, 추가적인 적분을 수행하지 않기 때문에 해석이 매우 빠르게 됩니다. 트리거링 적분 단계에서 보간 기능만 수행 합니다.

이벤트 억제(Event Suppression)

그러나 이 모든 것에도 불구하고 Decay1Decay2 모델에서 본 문제를 피하는 방법은 여전히 명확하지 않습니다.이에 대한 답은 noEvent 라는 특수 연산자 입니다. noEvent 연산자는 특수한 이벤트 처리를 억제합니다. 그리고, 대부분의 사용자가 처음에 일어날 것으로 예상했었던 모든 x 값에 대한 조건식을 연산합니다. 다음 모델에서 noEvent 연산자가 동작하는 것을 볼 수 있습니다.

model Decay3
  Real x;
initial equation
  x = 1;
equation
  der(x) = if noEvent(x>=0) then -sqrt(x) else 0;
end Decay3;

그리고, 결과는 여기에서 볼 수 있습니다.

/static/_images/Decay3.svg

이제 시뮬레이션이 아무 문제 없이 완료됩니다. noEvent 를 사용하면 sqrt(x) 가 음수 값 x 로 호출되지 않도록 하기 때문입니다.

가장 직관적인 동작이라고 생각하는 것을 얻기 위해 noEvent``연산자를 명시적으로 포함해야 한다는 것이 이상하게 보일 있습니다.가장 직관적인 동작을 기본 동작으로 만드는 것은 어떨까요? "이 질문에 대한 답은 "성능" 입니다.기존과 같이 조건식을 사용하여 이벤트를 생성하면 동작의 급격한 변화가 예상되는 시점에 대해서 솔버에 단서를 제공하여 시뮬레이션 성능이 향상됩니다.대부분의 경우 방법은 문제를 일으키지 않습니다. 장에서 제시한 예제는 문제를 강조하기 위해 고안되었지만, 대부분의 경우를 대표하지 않는 입니다.이러한 이유로 ``noEvent 는 기본값이 아니고 명시적으로 사용해야 하며, 동작이 원활하게 전환될 때만 사용해야 합니다. 그렇지 않으면 성능 문제가 발생할 수 있습니다.

채터링(Chattering)

모델리카를 사용하면 조만간 겪게 될 "채터링"이라는 일반적인 효과가 있습니다.다음 모델을 생각해 보겠습니다.

model WithChatter
  Real x;
initial equation
  x = 2;
equation
  der(x) = if x>=1 then -1 else 1;
end WithChatter;

사실상 이 모델은 x 의 초기 값에서 선형적으로 1을 향해 동작하도록 의도 되었습니다.수학적으로 말하면 x 의 값이 1이 되면 그냥 거기있있는 것입니다. 1보다 크든 작든 1에서 벗어나면 즉시 1로 돌아가기 때문입니다.

그러나 방정식을 엄격한 수학적 방법으로 풀지 않고, 부동 소수점 표현과 수치 적분기를 사용할 것입니다. 따라서, 내용에 대한 정밀도 및 적분 오류에 따라 제한이 발생합니다. 최종 효과는 x 의 궤적이 정확히 1로 유지되지 않고 위와 아래에서 약간 벗어나게 된다는 것입니다.그런데, 이런 일이 발생할 때마다 이벤트가 생성됩니다.

이 모델을 시뮬레이션하면 다음과 같은 결과가 나타납니다.

/static/_images/WC.svg

이러한 종류의 모델에서의 현상은 "채터링"으로 알려진 효과 입니다. 채터링은 단순히 솔버가 수행하는 많은 이벤트 발생으로 인해 시뮬레이션 시간 간격이 인위적으로 줄어드는 성능 저하를 의미합니다.시뮬레이션 중에 소요된 CPU 시간을 보면 시뮬레이션 성능에 미치는 영향이 분명합니다. x 가 1에 가까워지면 극적으로 상승하기 시작합니다. 이는 뒷 부분에서, 수행되는 계산의 수를 극적으로 증가시키는 많은 매우 작은 시뮬레이션 시간 간격을 이벤트가 유발하기 때문입니다. 겉보기에는 수학적 해가 명백해 보이지만 이벤트 빈도가 높기 때문에 여전히 시뮬레이션 성능이 저하되는 것이 WithChatter 예제에서 중요한 부분 입니다.

이것은 noEvent 연산자가 우리를 도울 수 있는 또 다른 경우입니다.다음과 같이 noEvent 연산자를 사용하여 조건식에 의해 생성되는 이벤트를 억제할 수 있습니다.

model WithoutChatter
  Real x;
initial equation
  x = 2;
equation
  der(x) = if noEvent(x>=1) then -1 else 1;
end WithoutChatter;

이렇게 하면 거의 동일한 해을 얻을 수 있지만 시뮬레이션 성능은 더 좋아집니다.

/static/_images/WOC.svg

x 가 1에 도달하기 전과 후에 CPU 사용량의 기울기에 눈에 띄는 변화가 없다는 점을 확인했습니다.``WithChatter`` 의 경우에서 볼 수 있는 것과 상당한 차이 가 있음을 비교해 볼 수 있습니다.

실제로 이와 같은 방정식은 흔하지 않으며, 채터링의 영향을 명확하게 보여주기 위해 극단적인 경우를 사용했습니다.그리고 여기에 설명한 동작은 특별히 현실적이거나 물리적이지 않습니다. 이 경우 시뮬레이션 성능에 미치는 영향을 명확하게 보여주기 위해 효과를 과장했습니다.

실제 세계에서 더 일반적인 예제는 안정적인 지점에 있는 조건식을 특징으로 합니다(Decay2 가 좋은 예입니다).이 경우 조건식이 발생하는 지점 또는 그 부근에서 시스템이 자연스럽게 안착하는 경향이 있어 채터링이 발생 합니다. 이때 정밀도와 수치적인 사항으로 인해 조건식과 관련된 이벤트가 자주 트리거됩니다. 그 효과는 시스템에 많은 구성요소가 모두 이러한 평형점에 있거나 그 근처에 있는 경우에 악화됩니다.

속도 vs 정확성(Speed vs. Accuracy)

지금까지의 논의를 통해, 어떤 경우 이벤트를 억제해야 하는지에 대한 이유를 명확히 이해 했을 것이라 생각합니다.그러면, 이벤트를 항상 건너뛰고 조건식을 해석하는 것은 어떨까요?시간을 내어 이 질문을 살펴보고 전체적으로 이벤트를 조건식과 연결하는 것이 매우 좋은 생각인 이유를 설명 하겠습니다. [2]

이벤트 감지가 없으면 적분기는 단순히 이벤트를 건너뛸 것입니다. 이런 일이 발생하면 적분기는 동작의 중요한 변경 사항을 놓치게 되며 이는 시뮬레이션의 정확도에 상당한 영향을 미칩니다.이는 대부분의 적분 루틴의 정확성이 기본 함수 및 파생 함수의 연속성에 대한 가정을 기반으로 하기 때문입니다. 이러한 가정이 위반되면 적분 루틴에 알려서 동작의 변화를 설명할 수 있도록 해야 합니다.

여기에서 이벤트가 발생합니다.동작 변경이 발생한 지점에서 적분을 강제로 중지한 다음, 동작 변경이 발생한 후 다시 시작합니다.그 결과 정확도는 더 높아지지만 시뮬레이션 속도가 느려지는 것 입니다.구체적인 예를 확인하기 위해 다음과 같은 간단한 모델리카 모델을 살펴 보겠습니다.

model WithEvents "Integrate with events"
  parameter Real freq = 1.0;
  Real x(start=0);
  Real y = time;
equation
  der(x) = if sin(2*Modelica.Constants.pi*freq*time)>0 then 2.0 else 0.0;
end WithEvents;

이 시스템을 보면 x 의 도함수 절반은 2 가 되고 x 의 도함수 나머지 절반은 0 입니다.따라서 이러한 각 주기에서 x 의 평균 도함수는 1 이어야 합니다. 이는 각 주기의 끝에서 xy 가 같아야 함을 의미합니다.

만약 WithEvents 모델에 대해서 시뮬레이션 한 결과는 다음과 같습니다.

/static/_images/WE.svg

xy 가 각 주기의 끝 마다 만나는 궤적을 확인할 수 있습니다.이는 기본 적분의 정확성을 시각적으로 나타냅니다. 그리고 기본 주기의 빈도를 높이더라도 이러한 특성이 유지됨을 알 수 있습니다.

/static/_images/WEf.svg

그러나 이제 정확히 동일한 적분 파라미터를 사용하지만 다음과 같이 noEvents 연산자를 사용하여이벤트를 억제하는 경우를 살펴 보겠습니다.

model WithNoEvents "Integrate without events"
  parameter Real freq = 1.0;
  Real x(start=0);
  Real y = time;
equation
  der(x) = noEvent(if sin(2*Modelica.Constants.pi*freq*time)>0 then 2.0 else 0.0);
end WithNoEvents;

이 경우 적분기는 동작의 변화를 인식하지 못합니다. 정확하게 적분하기 위해 최선을 다하지만 동작의 변화가 발생하는 위치에 대한 명시적인 지식이 없으면맹목적으로 계속 잘못된 도함수 값을 사용하고 동작 변화를 훨씬 넘어서 외삽할 것입니다.동일한 적분기 설정을 사용하여 WithNoEvents 모델을 시뮬레이션하면 결과가 얼마나 다른지 아래와 같이 확인할 수 있습니다.

/static/_images/WNE.svg

적분기가 얼마나 빨리 상당한 오류를 발생시키는지 확인 할 수 있습니다.

/static/_images/WNEf.svg

이 예제에서 사용된 적분에 대한 설정은 noEvent 연산자가 정확도에 미칠 수 있는 영향을 명확히 보여주기 위해 선택 되었습니다. 보다 일반적인 설정을 사용하면 결과의 차이가 그렇게 극적이지는 않을 것입니다.또한 noEvent 사용의 영향은 솔버마다 크게 다르기 때문에 예측하거나 정량화할 수 없습니다. 그러나 기본적인 요점은 명확합니다. noEvent 연산자를 사용하면 시뮬레이션 정확도의 결과에 상당한 영향을 미칠 수 있습니다.