첨부 소스 코드는 나눔고딕코딩 폰트를 사용합니다.
728x90
반응형
728x170

TestProject.zip
0.01MB

▶ 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
    }
}

 

728x90

 

▶ 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
    }
}
728x90
반응형
그리드형(광고전용)
Posted by icodebroker

댓글을 달아 주세요