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

보간(Interpolation)

보간(Interpolation)

이 장에서는 간단한 1차원 보간 방식을 구현하는 다양한 방법을 예로 들어 보겠습니다.모델리카 만으로 작성된 방식으로 시작한 다음 모델리카와 C를 결합한 대체 방법으로 구현한 것을 살펴 보겠습니다. 그런 다음 각 접근 방식의 장점과 단점에 대해 설명합니다.

모델리카로 구현(Modelica Implementation)

함수의 정의(Function Definition)

이 예제에서는 데이터를 다음 형식으로 보간한다고 가정합니다.

Independent Variable, x

Dependent Variable, y

x_1

y_1

x_2

y_2

x_3

y_3

...

...

x_n

y_n

이 데이터는 x_i<x_{i+1} 라고 가정 하겠습니다.

데이터에서 독립 변수 x 의 값이 주어지면 함수는 y 에 대해 보간된 값을 반환해야 합니다.이러한 기능은 모델리카에서 다음과 같이 구현될 수 있습니다.

function InterpolateVector "Interpolate a function defined by a vector"
  input Real x         "Independent variable";
  input Real ybar[:,2] "Interpolation data";
  output Real y        "Dependent variable";
protected
  Integer i;
  Integer n = size(ybar,1) "Number of interpolation points";
  Real p;
algorithm
  assert(x>=ybar[1,1], "Independent variable must be greater than or equal to "+String(ybar[1,1]));
  assert(x<=ybar[n,1], "Independent variable must be less than or equal to "+String(ybar[n,1]));
  i := 1;
  while x>=ybar[i+1,1] loop
    i := i + 1;
  end while;
  p := (x-ybar[i,1])/(ybar[i+1,1]-ybar[i,1]);
  y := p*ybar[i+1,2]+(1-p)*ybar[i,2];
end InterpolateVector;

이 기능을 하나씩 살펴보고 무슨 일이 일어나고 있는지 이해해 보겠습니다.인자(argument) 선언부터 알아보겠습니다.

  input Real x         "Independent variable";
  input Real ybar[:,2] "Interpolation data";
  output Real y        "Dependent variable";

input 전달 인자(argument) x 는 함수를 보간에 사용하려는 독립 변수의 값을 나타내고 input 전달 인자 ybar 는 보간 데이터를 나타내며 output 전달 인자 y 는 보간된 값을 나타냅니다. 함수의 다음 구문을 살펴보겠습니다.

protected
  Integer i;
  Integer n = size(ybar,1) "Number of interpolation points";
  Real p;

다양한 protected 변수의 선언이 되어 있으며, 다항식 해석(Polynomial Evaluation) 예제에서 보았듯이 이 변수들은 함수에 의해 내부적으로 사용하는 사실상 매개 변수입니다. 이 경우 i 는 인덱스 변수로 사용되고 n 은 보간 데이터의 데이터 포인트 수이며 p 는 보간 방식에 사용하는 가중치를 나타냅니다.

모든 변수 선언이 완료 되었으므로, 이제 함수의 algorithm 섹션을 구현할 수 있습니다.

algorithm
  assert(x>=ybar[1,1], "Independent variable must be greater than or equal to "+String(ybar[1,1]));
  assert(x<=ybar[n,1], "Independent variable must be less than or equal to "+String(ybar[n,1]));
  i := 1;
  while x>=ybar[i+1,1] loop
    i := i + 1;
  end while;
  p := (x-ybar[i,1])/(ybar[i+1,1]-ybar[i,1]);
  y := p*ybar[i+1,2]+(1-p)*ybar[i,2];

처음 두 문장은 x 값이 [x_1, x_n] 간격 내에 있는지 확인하는 assert 문장입니다. 그렇지 않은 경우 보간이 실패한 이유를 설명하는 오류 메시지가 생성됩니다.

나머지 함수는 x_i<=x<x_{i+1} 과 같은 i 값을 검색합니다.``i`` 값이 식별되면 보간된 값은 다음과 같이 간단하게 계산합니다.

y = p\ \bar{y}_{i+1,2}+(1-p)\ \bar{y}_{i,2}

이를 다시 정리하면 아래와 같은 수식을 얻습니다.

p = \frac{x-\bar{y}_{i,1}}{\bar{y}_{i+1,1}-\bar{y}_{i,1}]}

테스트 케이스(Test Case)

이제 이 기능을 모델 내에서 사용하여 테스트해 보겠습니다. 간단한 테스트 사례로 보간 함수에서 반환한 값을 적분 하는 것으로 검증하는 것 입니다.다음 데이터를 이용해서 기능을 검증하는 기초 데이터로 사용하겠습니다.

x

y

0

0

2

0

4

2

6

0

8

0

이 데이터를 선도로 표현하면 보간된 함수가 다음과 같이 표시됩니다.

/static/_images/interpolation-1.svg

다음 모델에서 독립 변수 xtime 과 동일하게 설정 하며, 샘플 데이터는 변수 y 에 대한 값을 보간하는 데 사용합니다. 그런 다음 y 값을 통합하여 z 를 계산합니다.

model IntegrateInterpolatedVector "Exercises the InterpolateVector"
  Real x;
  Real y;
  Real z;
equation
  x = time;
  y = InterpolateVector(x, [0.0, 0.0; 2.0, 0.0; 4.0, 2.0; 6.0, 0.0; 8.0, 0.0]);
  der(z) = y;
  annotation ...
end IntegrateInterpolatedVector;

다음 플롯에서 이 모델의 시뮬레이션 결과를 볼 수 있습니다.

/static/_images/IIV.svg

이 접근 방식에는 몇 가지 단점이 있습니다. 첫 번째는 함수가 사용하는 모든 곳에 데이터를 전달해야 한다는 것입니다. 또한 더 높은 차원의 보간 방식의 경우 필요한 데이터가 복잡하고(불규칙한 그리드의 경우) 클 수 있습니다.따라서 모델리카 소스 코드에 데이터를 저장하는 것이 반드시 편리한 것은 아니기 때문에, 예를 들어 데이터를 외부 파일에 저장하는 것이 더 나을 수 있습니다. 그러나 모델리카 소스 코드 이외의 소스에서 보간 데이터를 채우려면 ExternalObject 를 사용해야 합니다.

ExternalObject 사용하기(Using an ExternalObject)

ExternalObject 타입은 (당연하게도) 모델리카 소스 코드에 표현되지 않은 정보를 참조하는 데 사용하는 특수 유형입니다. ExternalObject 타입의 주요 용도는 모델리카 소스 코드 외부에서 유지되는 데이터 또는 상태를 나타내는 것입니다.이것은 곧 보게 될 보간 데이터일 수도 있고 자체 상태를 유지하는 다른 소프트웨어 시스템을 나타내는 것일 수도 있습니다.

테스트 케이스(Test Case)

이 예제에서는 앞서 설명한 방식과 상황을 뒤집어 테스트 사례부터 시작하겠습니다. 이것은 ExternalObject 가 어떻게 사용하는지에 대한 몇 가지 유용한 컨텍스트를 제공할 것입니다.테스트 케이스의 모델리카 소스 코드는 다음과 같습니다.

model IntegrateInterpolatedExternalVector
  "Exercises the InterpolateExternalVector"
  parameter VectorTable vector = VectorTable(ybar=[0.0, 0.0;
                                                   2.0, 0.0;
                                                   4.0, 2.0;
                                                   6.0, 0.0;
                                                   8.0, 0.0]);
  Real x;
  Real y;
  Real z;
equation
  x = time;
  y = InterpolateExternalVector(x, vector);
  der(z) = y;
  annotation ...
end IntegrateInterpolatedExternalVector;

여기에서 이전 테스트 케이스와 주요 차이점은 데이터를 보간 함수에 직접 전달하지 않는다는 사실입니다. 대신, 자료형이 VectorTable 인 특수 변수 vector 를 생성합니다. 곧 VectorTable 이 무엇인지 정확히 논의할 예정이기 때문에, 지금은 보간 데이터를 나타낸다고 만 생각하겠습니다.``vector`` 객체 생성을 제외하고 모델의 나머지 부분은 InterpolateExternalVector 함수를 사용하여 보간을 수행하고, 원시 보간 데이터 대신 해당 함수에 vector 변수를 전달한다는 점을 제외하면 이전 사례와 거의 동일합니다.

이 모델을 시뮬레이션하면 결과가 이전 테스트 사례와 비교할 때 예상했던 것과 정확히 일치함을 알 수 있습니다.

/static/_images/IIEV.svg

ExternalObject 정의하기(Defining an ExternalObject)

무엇보다 먼저 검증한 시험 사례가 어떻게 구현되었는지 확인하기 위해, VectorTable 자료형이 어떻게 구현되었는지 살펴보겠습니다. 언급했듯이 VectorTableExternalObject 자료형입니다. 이것은 종종 "불투명(opaque,완전히 정이되어 있지 않은 상태)" 포인터라고 하는 것을 나타내는 데 사용하는 모델리카의 특수 자료형입니다.이는 ExternalObject 가 (모델리카에서) 직접 액세스할 수 없는 일부 데이터를 나타냄을 의미합니다.

VectorTable 자료형을 다음과 같이 구현합니다.

type VectorTable "A vector table implemented as an ExternalObject"
  extends ExternalObject;
  function constructor
    input Real ybar[:,2];
    output VectorTable table;
    external "C" table=createVectorTable(ybar, size(ybar,1))
      annotation ...
  end constructor;

  function destructor "Release storage"
    input VectorTable table;
    external "C" destroyVectorTable(table)
      annotation ...
  end destructor;
end VectorTable;

VectorTableExternalObject 자료형에서 상속된다는 점을 생각해 봐야 합니다. ExternalObject 는 정의 내에서 구현된 constructor 함수와 destructor 함수라는 두 가지 특수 함수를 가질 수 있습니다. 이 두 기능 모두 여기에 표시됩니다.

생성자(Constructor)

constructor 함수는 VectorTable 인스턴스가 생성될 때 호출됩니다(예: 테스트 사례에서 vector 변수의 선언). 이 constructor 함수는 불투명 포인터를 초기화하는 데 사용합니다. 초기화 프로세스의 일부로 필요한 데이터는 무엇이든 constructor 함수에 대한 인수로 전달되어야 합니다. 동일한 데이터가 인스턴스화 중에 존재해야 합니다(예를들어, vector 변수 선언의 데이터 인자).

constructor 함수의 정의는 이전 예제와 달리 algorithm 섹션을 포함하지 않기 때문에 일반적이지 않습니다. algorithm 섹션은 일반적으로 함수의 반환 값을 계산하는 데 사용하는데, 그 대신에 constructor 함수에는 external 절이 있습니다. 이는 모델리카 이외의 다른 언어로 기능이 구현되었음을 나타냅니다. 이 경우 다른 언어는 C입니다(external 키워드 다음에 나오는 "C" 로 표시됨).이것은 table 변수(이 함수의 출력 이며 불투명한 포인터를 나타냄)가``createVectorTable`` 이라는 C 함수와 ybar 변수의 크기에 의해 반환된다는 것을 알려줍니다.

createVectorTable 호출 다음에는 annotation 이 있습니다. 이 주석은 이 외부 C 함수의 소스 코드를 찾을 위치를 모델리카 컴파일러에 알려줍니다.

여기서 중요한 점은 모델리카 컴파일러의 관점에서 볼 때 VectorTablecreateVectorTable 에 의해 반환된 불투명한 포인터일 뿐이라는 것입니다. 모델리카에서 이 포인터 뒤에 있는 데이터에 액세스하는 것은 불가능합니다.

소멸자(Destructor)

destructor 함수는 ExternalObject 가 더 이상 필요하지 않을 때마다 호출됩니다. 이렇게 하면 모델리카 런타임이 ExternalObject 에서 사용하는 메모리를 정리할 수 있습니다. 모델에서 인스턴스화된 ExternalObject 는 일반적으로 시뮬레이션이 끝날 때까지 지속됩니다. 그러나 예를 들어 함수에서 protected 변수로 선언된 ExternalObject 는 단일 표현식 해석 과정에서 생성되고 소멸될 수 있습니다.따라서 ExternalObject 에 의해 할당된 메모리가 해제되었는지 확인하는 것이 중요합니다.

일반적으로 destructor 함수는 외부 함수로도 구현됩니다. 이 경우 모델리카에서 destructor 함수를 호출하면 VectorTable 인스턴스가 인수로 전달되는 C 함수 destroyVectorTable 이 호출됩니다. 해당 VectorTable 인스턴스와 관련된 모든 메모리는 destructor 호출로 해제되어야 합니다. 다시, destoryVectorTable 함수의 소스 코드를 찾을 위치를 모델리카 컴파일러에 알리는 데 사용하는 동일한 자료형의 annotations을 볼 수 있습니다.

외부 C 코드(External C Code)

이전의 예제에서 호출했던 외부 C 함수는 다음과 같이 구현됩니다.

#ifndef _VECTOR_TABLE_C_
#define _VECTOR_TABLE_C_

#include <stdlib.h>
#include "ModelicaUtilities.h"

/*
  Here we define the structure associated
  with our ExternalObject type 'VectorTable'
*/
typedef struct {
  double *x; /* Independent variable values */
  double *y; /* Dependent variable values */
  size_t npoints; /* Number of points in this data */
  size_t lastIndex; /* Cached value of last index */
} VectorTable;

void *
createVectorTable(double *data, size_t np) {
  VectorTable *table = (VectorTable*) malloc(sizeof(VectorTable));
  if (table) {
    /* Allocate memory for data */
    table->x = (double*) malloc(sizeof(double)*np);
    if (table->x) {
      table->y = (double*) malloc(sizeof(double)*np);
      if (table->y) {
        /* Copy data into our local array */
        size_t i;
        for(i=0;i<np;i++) {
          table->x[i] = data[2*i];
          table->y[i] = data[2*i+1];
        }
        /* Initialize the rest of the table object */
        table->npoints = np;
        table->lastIndex = 0;
      }
      else {
        free(table->x);
        free(table);
        table = NULL;
        ModelicaError("Memory allocation error\n");
      }
    }
    else {
      free(table);
      table = NULL;
      ModelicaError("Memory allocation error\n");
    }
  }
  else {
    ModelicaError("Memory allocation error\n");
  }
  return table;
}

void
destroyVectorTable(void *object) {
  VectorTable *table = (VectorTable *)object;
  if (table==NULL) return;
  free(table->x);
  free(table->y);
  free(table);
}

double
interpolateVectorTable(void *object, double x) {
  VectorTable *table = (VectorTable *)object;
  size_t i = table->lastIndex;
  double p;

  ModelicaFormatMessage("Request to compute value of y at %g\n", x);
  if (x<table->x[0])
    ModelicaFormatError("Requested value of x=%g is below the lower bound of %g\n",
      x, table->x[0]);
  if (x>table->x[table->npoints-1])
    ModelicaFormatError("Requested value of x=%g is above the upper bound of %g\n",
      x, table->x[table->npoints-1]);

  while(i<table->npoints-1&&x>table->x[i+1]) i++;
  while(i>0&&x<table->x[i]) i--;

  p = (x-table->x[i])/(table->x[i+1]-table->x[i]);
  table->lastIndex = i;
  return p*table->y[i+1]+(1-p)*table->y[i];
}

#endif

이 책은 C 프로그래밍 언어에 관한 책이 아니므로 이 코드에 대한 철저한 검토와 코드가 정확히 어떻게 작동하는지에 대해서 다루는 것은 책의 범위를 벗어납니다.하지만 이 파일의 내용을 다음과 같이 요약할 수 있습니다.

먼저 VectorTable 이라는 struct 는 모델리카에서 VectorTable 자료형과 관련된 데이터입니다. 여기에는 보간 데이터(xy 멤버 형식)뿐만 아니라 데이터 포인트 수 npoints 및 마지막으로 사용된 인덱스에 대해 캐시된 값 lastIndex 도 포함됩니다.

다음으로 VectorTable 구조의 인스턴스를 할당하고 그 안의 모든 데이터를 초기화하는 createVectorTable 함수를 보면, 해당 인스턴스가 모델리카 런타임으로 반환 되는 것을 알 수 있습니다.``createVectorTable`` 정의 다음에는 createVectorTable 에 의해 수행된 작업을 효과적으로 취소하는 destroyVectorTable 정의가 있습니다.

마지막으로 interpolateVectorTable 함수를 보겠습니다. 이것은 VectorTable 구조의 인스턴스와 독립 변수에 대한 값을 전달하고 종속 변수에 대한 보간된 값을 반환하는 C 함수인데, 앞에서 제시한 InterpolateVector 함수와 거의 동일한 기능을 수행합니다. 모델리카 런타임은 외부 C 코드가 오류를 보고할 수 있도록 ModelicaFormatError 와 같은 기능을 제공합니다. interpolateVectorTable 함수는 이전에 InterpolateVector 에서 보았던 내용을 구현하는 데 사용합니다. i 조회는 매번 1부터 시작하는 대신 interpolateVectorTable 에 대한 마지막 호출에서 찾은 i 값에서 시작한다는 점을 제외하면 기본적으로 동일합니다.

보간(Interpolation)

interpolateVectorTable 이 어떻게 정의되는지 보았지만 지금까지 그것이 어디에 사용하는지는 보지 못했습니다. InterpolateVector 와 거의 같은 역할을 수행하지만, VectorTable 객체를 사용하여 보간 데이터를 표현한다고 언급했습니다. 모델리카에서 interpolateVectorTable 을 호출하려면 다음과 같이 모델리카 함수를 정의하기만 하면 됩니다.

function InterpolateExternalVector
  "Interpolate a function defined by a vector using an ExternalObject"
  input Real x;
  input VectorTable table;
  output Real y;
  external "C" y = interpolateVectorTable(table, x)
    annotation ...
end InterpolateExternalVector;

이전에 VectorTable 이 불투명하고 모델리카 코드가 VectorTable 에 포함된 데이터에 액세스할 수 없다고 언급했습니다. 모델리카 함수 InterpolateExternalVector 는 보간 데이터에 접근 할 수 있는 C 대응 interpolateVectorTable 을 호출하여 보간을 수행합니다.

논의(Discussion)

이전에 논의한 바와 같이 초기 보간 방식에서는 다루기 힘든 많은 양의 데이터를 전달해야 했습니다. VectorTable 을 구현함으로써 그 데이터를 단일 변수로 나타낼 수 있었습니다.

ExternalObject 접근 방식에 대해 주목해야 할 중요한 점은 초기화 데이터가 모델리카 소스 코드 완전히 외부에 있을 수 있다는 것입니다. 단순화를 위해 이 섹션에 표시된 예제 코드는 데이터 배열을 사용하여 VectorTable 을 초기화했지만, 파일 이름 을 초기화 코드에 쉽게 전달할 수 있습니다. 그 파일은 createVectorTable 함수에 의해 읽힐 수 있고 VectorTable 구조의 내용은 그 파일의 데이터를 사용하여 초기화될 수 있습니다. 대부분의 경우 이러한 접근 방식은 데이터 관리를 더 쉽게 할 뿐만 아니라 C를 활용하면 더 복잡한(신규 또는 기존) 알고리즘을 사용할 수 있습니다.

다음 섹션 에는 모델리카에서 외부 C 코드를 호출할 수 있는 방법에 대한 또 다른 예가 포함되어 있습니다.