■ 다항식 최소 제곱법(Polynomial Least Squares Method) 사용하기

------------------------------------------------------------------------------------------------------------------------

 


TestProject.zip


MathHelper.cs

 

 

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Linq;

 

namespace TestProject

{

    /// <summary>

    /// 수학 헬퍼

    /// </summary>

    public static class MathHelper

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Static

        //////////////////////////////////////////////////////////////////////////////// Public

 

        #region Y 구하기 - GetY(coefficientList, x)

 

        /// <summary>

        /// Y 구하기

        /// </summary>

        /// <param name="coefficientList">계수 리스트</param>

        /// <param name="x">X</param>

        /// <returns>Y</returns>

        public static double GetY(List<double> coefficientList, double x)

        {

            double result  = 0d;

            double xFactor = 1d;

 

            for(int i = 0; i < coefficientList.Count; i++)

            {

                result += xFactor * coefficientList[i];

 

                xFactor *= x;

            }

 

            return result;

        }

 

        #endregion

        #region 제곱 오차 구하기 - GetErrorSquared(pointList, coefficientList)

 

        /// <summary>

        /// 제곱 오차 구하기

        /// </summary>

        /// <param name="pointList">포인트 리스트</param>

        /// <param name="coefficientList">계수 리스트</param>

        /// <returns>제곱 오차</returns>

        public static double GetErrorSquared(List<PointF> pointList, List<double> coefficientList)

        {

            double result = 0;

 

            foreach(PointF point in pointList)

            {

                double error = point.Y - GetY(coefficientList, point.X);

 

                result += error * error;

            }

 

            return result;

        }

 

        #endregion

        #region 다항식 최소 제곱법 해 구하기 - GetPolynomialLeastSquaresFit(pointList, degree)

 

        /// <summary>

        /// 다항식 최소 제곱법 해 구하기

        /// </summary>

        /// <param name="pointList">포인트 리스트</param>

        /// <param name="degree">차수</param>

        /// <returns>다항식 최소 제곱법</returns>

        public static List<double> GetPolynomialLeastSquaresFit(List<PointF> pointList, int degree)

        {

            double[,] coefficientArray = new double[degree + 1, degree + 2];

 

            for(int i = 0; i <= degree; i++)

            {

                coefficientArray[i, degree + 1] = 0;

 

                foreach(PointF point in pointList)

                {

                    coefficientArray[i, degree + 1] -= Math.Pow(point.X, i) * point.Y;

                }

 

                for(int j = 0; j <= degree; j++)

                {

                    coefficientArray[i, j] = 0;

 

                    foreach(PointF point in pointList)

                    {

                        coefficientArray[i, j] -= Math.Pow(point.X, j + i);

                    }

                }

            }

 

            double[] resultArray = ProcessGaussianElimination(coefficientArray);

 

            return resultArray.ToList<double>();

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////// Private

 

        #region 가우시안 제거 처리하기 - ProcessGaussianElimination(coefficientArray)

 

        /// <summary>

        /// 가우시안 제거 처리하기

        /// </summary>

        /// <param name="coefficientArray">계수 배열</param>

        /// <returns>가우시안 제거 배열</returns>

        private static double[] ProcessGaussianElimination(double[,] coefficientArray)

        {

            int maximumEquation    = coefficientArray.GetUpperBound(0);

            int maximumCoefficient = coefficientArray.GetUpperBound(1);

 

            for(int i = 0; i <= maximumEquation; i++)

            {

                if(coefficientArray[i, i] == 0)

                {

                    for(int j = i + 1; j <= maximumEquation; j++)

                    {

                        if(coefficientArray[j, i] != 0)

                        {

                            for(int k = i; k <= maximumCoefficient; k++)

                            {

                                double temporary = coefficientArray[i, k];

 

                                coefficientArray[i, k] = coefficientArray[j, k];

                                coefficientArray[j, k] = temporary;

                            }

 

                            break;

                        }

                    }

                }

 

                double coefficient_i_i = coefficientArray[i, i];

 

                if(coefficient_i_i == 0)

                {

                    throw new ArithmeticException

                    (

                        string.Format

                        (

                            "이 포인트들에 대한 유일한 해가 없습니다.",

                            coefficientArray.GetUpperBound(0) - 1

                        )

                    );

                }

 

                for(int j = i; j <= maximumCoefficient; j++)

                {

                    coefficientArray[i, j] /= coefficient_i_i;

                }

 

                for(int j = 0; j <= maximumEquation; j++)

                {

                    if(j != i)

                    {

                        double coefficient_j_i = coefficientArray[j, i];

 

                        for(int d = 0; d <= maximumCoefficient; d++)

                        {

                            coefficientArray[j, d] -= coefficientArray[i, d] * coefficient_j_i;

                        }

                    }

                }

            }

 

            double[] solutionArray = new double[maximumEquation + 1];

 

            for(int i = 0; i <= maximumEquation; i++)

            {

                solutionArray[i] = coefficientArray[i, maximumCoefficient];

            }

 

            return solutionArray;

        }

 

        #endregion

    }

}

 

 

MainForm.cs

 

 

using System;

using System.Collections.Generic;

using System.Drawing;

using System.Drawing.Drawing2D;

using System.Windows.Forms;

 

namespace TestProject

{

    /// <summary>

    /// 메인 폼

    /// </summary>

    public partial class MainForm : Form

    {

        //////////////////////////////////////////////////////////////////////////////////////////////////// Field

        ////////////////////////////////////////////////////////////////////////////////////////// Private

 

        #region Field

 

        /// <summary>

        /// X 최소값

        /// </summary>

        private const float X_MINIMUM = -10.0f;

 

        /// <summary>

        /// X 최대값

        /// </summary>

        private const float X_MAXIMUM = 10.0f;

 

        /// <summary>

        /// Y 최소값

        /// </summary>

        private const float Y_MINIMUM = -10.0f;

 

        /// <summary>

        /// Y 최대값

        /// </summary>

        private const float Y_MAXIMUM = 10.0f;

 

        /// <summary>

        /// 그리기 변환

        /// </summary>

        private Matrix drawingTransform;

 

        /// <summary>

        /// 반전 변환

        /// </summary>

        private Matrix inverseTransform;

 

        /// <summary>

        /// 솔루션 여부

        /// </summary>

        private bool hasSolution = false;

 

        /// <summary>

        /// 포인트 리스트

        /// </summary>

        private List<PointF> pointList = new List<PointF>();

 

        /// <summary>

        /// 최적 계수 리스트

        /// </summary>

        private List<double> bestCoefficientList;

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Constructor

        ////////////////////////////////////////////////////////////////////////////////////////// Public

 

        #region 생성자 - MainForm()

 

        /// <summary>

        /// 생성자

        /// </summary>

        public MainForm()

        {

            InitializeComponent();

 

            #region 이벤트를 설정한다.

 

            Load                            += Form_Load;

            this.calculateButton.Click      += calculateButton_Click;

            this.clearButton.Click          += clearButton_Click;

            this.graphButton.Click          += graphButton_Click;

            this.graphPictureBox.MouseClick += graphPictureBox_MouseClick;

            this.graphPictureBox.Paint      += graphPictureBox_Paint;

 

            #endregion

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////////////////////////// Method

        ////////////////////////////////////////////////////////////////////////////////////////// Private

        //////////////////////////////////////////////////////////////////////////////// Event

 

        #region 폼 로드시 처리하기 - Form_Load(sender, e)

 

        /// <summary>

        /// 폼 로드시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void Form_Load(object sender, EventArgs e)

        {

            RectangleF worldRectangle = new RectangleF

            (

                X_MINIMUM,

                Y_MINIMUM,

                X_MAXIMUM - X_MINIMUM,

                Y_MAXIMUM - Y_MINIMUM

            );

 

            PointF[] pointArray =

            {

                new PointF(0, graphPictureBox.ClientSize.Height),

                new PointF(graphPictureBox.ClientSize.Width, graphPictureBox.ClientSize.Height),

                new PointF(0, 0),

            };

 

            this.drawingTransform = new Matrix(worldRectangle, pointArray);

 

            this.inverseTransform = this.drawingTransform.Clone();

 

            this.inverseTransform.Invert();

        }

 

        #endregion

        #region 계산하기 버튼 클릭시 처리하기 - calculateButton_Click(sender, e)

 

        /// <summary>

        /// 계산하기 버튼 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void calculateButton_Click(object sender, EventArgs e)

        {

            Cursor = Cursors.WaitCursor;

 

            this.coefficientTextBox.Clear();

            this.errorTextBox.Clear();

 

            Application.DoEvents();

 

            int degree = (int)this.degreeNumericUpDown.Value;

 

            this.bestCoefficientList = MathHelper.GetPolynomialLeastSquaresFit(this.pointList, degree);

 

            this.hasSolution = true;

 

            string coefficientList = string.Empty;

 

            foreach(double coefficient in this.bestCoefficientList)

            {

                coefficientList += " " + coefficient.ToString();

            }

 

            this.coefficientTextBox.Text = coefficientList.Substring(1);

 

            ShowError();

 

            this.hasSolution = true;

 

            this.graphPictureBox.Refresh();

 

            Cursor = Cursors.Default;

        }

 

        #endregion

        #region 지우기 버튼 클릭시 처리하기 - clearButton_Click(sender, e)

 

        /// <summary>

        /// 지우기 버튼 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void clearButton_Click(object sender, EventArgs e)

        {

            this.pointList = new List<PointF>();

 

            this.hasSolution = false;

 

            this.graphPictureBox.Refresh();

 

            this.coefficientTextBox.Clear();

            this.errorTextBox.Clear();

        }

 

        #endregion

        #region 그래프 버튼 클릭시 처리하기 - graphButton_Click(sender, e)

 

        /// <summary>

        /// 그래프 버튼 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void graphButton_Click(object sender, EventArgs e)

        {

            string[] coefficientList = this.coefficientTextBox.Text.Split();

 

            this.bestCoefficientList = new List<double>();

 

            foreach(string coefficient in coefficientList)

            {

                this.bestCoefficientList.Add(double.Parse(coefficient));

            }

 

            ShowError();

 

            this.graphPictureBox.Refresh();

        }

 

        #endregion

        #region 그래프 픽처 박스 마우스 클릭시 처리하기 - graphPictureBox_MouseClick(sender, e)

 

        /// <summary>

        /// 그래프 픽처 박스 마우스 클릭시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void graphPictureBox_MouseClick(object sender, MouseEventArgs e)

        {

            PointF[] pointArray = { new PointF(e.X, e.Y) };

 

            this.inverseTransform.TransformPoints(pointArray);

 

            this.pointList.Add(pointArray[0]);

 

            this.graphPictureBox.Refresh();

        }

 

        #endregion

        #region 그래프 픽처 박스 페인트시 처리하기 - graphPictureBox_Paint(sender, e)

 

        /// <summary>

        /// 그래프 픽처 박스 페인트시 처리하기

        /// </summary>

        /// <param name="sender">이벤트 발생자</param>

        /// <param name="e">이벤트 인자</param>

        private void graphPictureBox_Paint(object sender, PaintEventArgs e)

        {

            e.Graphics.Transform     = this.drawingTransform;

            e.Graphics.SmoothingMode = SmoothingMode.AntiAlias;

 

            DrawAxe(e.Graphics);

 

            if(this.hasSolution)

            {

                using(Pen pen = new Pen(Color.Blue, 0))

                {

                    const double xStep = 0.1;

 

                    double y0 = MathHelper.GetY(this.bestCoefficientList, X_MINIMUM);

 

                    for(double x = X_MINIMUM + xStep; x <= X_MAXIMUM; x += xStep)

                    {

                        double y1 = MathHelper.GetY(this.bestCoefficientList, x);

 

                        e.Graphics.DrawLine(pen, (float)(x - xStep), (float)y0, (float)x, (float)y1);

 

                        y0 = y1;

                    }

                }

            }

 

            const float deltaX = (X_MAXIMUM - X_MINIMUM) / 200;

            const float deltaY = (Y_MAXIMUM - Y_MINIMUM) / 200;

 

            using(Pen pen = new Pen(Color.Black, 0))

            {

                foreach(PointF point in this.pointList)

                {

                    e.Graphics.FillRectangle

                    (

                        Brushes.White,

                        point.X - deltaX,

                        point.Y - deltaY,

                        2 * deltaX,

                        2 * deltaY

                    );

 

                    e.Graphics.DrawRectangle

                    (

                        pen,

                        point.X - deltaX,

                        point.Y - deltaY,

                        2 * deltaX,

                        2 * deltaY

                    );

                }

            }

        }

 

        #endregion

 

        //////////////////////////////////////////////////////////////////////////////// Function

 

        #region 축 그리기 - DrawAxe(graphics)

 

        /// <summary>

        /// 축 그리기

        /// </summary>

        /// <param name="graphics">그래픽스</param>

        private void DrawAxe(Graphics graphics)

        {

            using(Pen pen = new Pen(Color.Black, 0))

            {

                const float xThick = 0.2f;

                const float yThick = 0.2f;

 

                graphics.DrawLine(pen, X_MINIMUM, 0, X_MAXIMUM, 0);

 

                for(float x = X_MINIMUM; x <= X_MAXIMUM; x += 1.0f)

                {

                    graphics.DrawLine(pen, x, -yThick, x, yThick);

                }

 

                graphics.DrawLine(pen, 0, Y_MINIMUM, 0, Y_MAXIMUM);

 

                for(float y = Y_MINIMUM; y <= Y_MAXIMUM; y += 1.0f)

                {

                    graphics.DrawLine(pen, -xThick, y, xThick, y);

                }

            }

        }

 

        #endregion

        #region 오차 보여주기 - ShowError()

 

        /// <summary>

        /// 오차 보여주기

        /// </summary>

        private void ShowError()

        {

            double error = Math.Sqrt(MathHelper.GetErrorSquared(this.pointList, this.bestCoefficientList));

 

            this.errorTextBox.Text = error.ToString();

        }

 

        #endregion

    }

}

 

------------------------------------------------------------------------------------------------------------------------

Posted by 사용자 icodebroker
TAG

댓글을 달아 주세요