Cách tra hướng dẫn của hàm trong matlab

Function trong MATLAB có gì khác với với function trong những ngôn ngữ lập trình khác? Bài viết này sẽ trình bày một số nội dung liên quan đến Function và các loại Function trong MATLAB.

  • Tìm hiểu thêm: Function là gì?

Mục lục

Function trong MATLAB là gì?

Function (Hàm) là một nhóm các câu lệnh cùng thực hiện một tác vụ. Trong MATLAB, các hàm được định nghĩa trong các tệp (file) riêng biệt. Tên của tệp và của hàm phải giống nhau.

Hàm hoạt động dựa trên các biến nằm trong vùng làm việc (workspace) của riêng chúng. Đây còn được gọi là vùng làm việc cục bộ. Nó khác với vùng làm việc mà bạn truy cập tại dấu nhắc lệnh MATLAB. Vùng này được gọi là vùng làm việc cơ sở.

Trong MATLAB, các hàm có thể nhận nhiều hơn một tham số (arguments) đầu vào. Tương tự, chúng cũng có thể trả về nhiều hơn một tham số đầu ra.

Cú pháp của một câu lệnh tạo hàm là:

function [out1,out2, …, outN] = myfun(in1,in2,in3, …, inN)

Các loại Function trong MATLAB

Có một số loại hàm khả dụng trong MATLAB. Chúng bao gồm: hàm cục bộ (Local Functions), hàm lồng nhau (Nested Functions), hàm riêng tư (Private Functions) và hàm ẩn danh (Anonymous Functions).

1. Hàm ẩn danh

Có thể hiểu, loại hàm này giống như một hàm nội tuyến (inline function) trong các ngôn ngữ lập trình truyền thống.

Đây là loại hàm được định nghĩa trong một câu lệnh MATLAB duy nhất. Nó bao gồm một biểu thức (expression) MATLAB duy nhất và không hạn chế số lượng tham số đầu vào và đầu ra.

Có nhiều cách để bạn định nghĩa hàm ẩn danh. Bạn có thể định nghĩa ngay tại dòng lệnh của MATLAB, trong một hàm hoặc trong một đoạn lệnh (script). Bằng những cách này, bạn có thể tạo hàm mà không cần phải tạo tệp cho chúng.

Cú pháp để tạo một hàm ẩn danh từ một biểu thức là:

2. Hàm cục bộ

Nếu một hàm không phải hàm ẩn danh, thì chúng phải được định nghĩa trong một tệp. Mỗi tệp hàm sẽ chứa một hàm chính (Primary Function) xuất hiện đầu tiên. Sau đó, tệp sẽ gồm bất kỳ hàm con (Sub-Function) tùy chọn nào được sử dụng.

Hàm chính có thể được gọi từ bất kỳ dòng lệnh hoặc hàm nào khác, ở ngoài tệp xác định chúng. Tuy nhiên, hàm con thì không giống vậy. Ta không thể gọi hàm con từ một dòng lệnh hoặc hàm nào nằm bên ngoài tệp hàm.

Hàm con chỉ hiển thị với hàm chính và hàm con khác nằm trong tệp hàm định nghĩa chúng.

3. Hàm lồng nhau

Bạn có thể định nghĩa các hàm bên trong phần nội dung của một hàm khác. Chúng được gọi là các hàm lồng nhau. Một hàm lồng nhau chứa một hoặc tất cả các thành phần của bất kỳ hàm nào khác.

Khi một hàm được định nghĩa bên trong một hàm khác, nó sẽ chia sẻ quyền truy cập vào vùng làm việc của hàm chứa nó.

Một hàm lồng nhau có thể được định nghĩa theo cú pháp sau:

Cách tra hướng dẫn của hàm trong matlab
Minh họa về hàm lồng nhau trong MATLAB

Trong ví dụ trên, chúng ta thầy rằng hàm y đang nằm trong nội dung của hàm x. Do đó, chúng được gọi là các hàm lồng nhau.

4. Hàm riêng tư

Đây là một hàm chính nhưng chỉ hiển thị cho một nhóm hàm giới hạn khác. Nếu bạn không muốn công khai việc triển khai hàm, bạn có thể tạo chúng dưới dạng các hàm riêng tư.

Hàm riêng tư sẽ nằm trong các thư mục con với tên đặc biệt là private. Chúng chỉ hiển thị với các hàm nằm trong thư mục mẹ.

Biến toàn cục và Function trong MATLAB

Điểm đặc biệt của biến toàn cục có thể được dùng chung bởi nhiều hơn một hàm. Để làm được điều này, bạn cần phải khai báo một biến là toàn cục trong tất cả các hàm của MATLAB.

Nếu bạn muốn truy cập biến đó từ vùng làm việc cơ sở, hãy khai báo biến ngay tại dòng lệnh.

Việc khai báo biến toàn cục phải được thực hiện trước khi sử dụng biến trong hàm. Thông thường, một cách hay là dùng chữ cái viết hoa cho tên biến toàn cục để phân biệt chúng với các biến thông thường khác.

Tóm lại, việc sử dụng hàm trong MATLAB cũng không quá phức tạp. Hy vọng qua bài viết này, bạn đã hiểu thêm về Function trong MATLAB và có cách để ứng dụng nó tốt hơn.

Trong chương này ta sẽ thảo luận một chút về cách dùng hàm, qua đó lập mối quan hệ giữa toán và MATLAB. Tiếp theo là ứng dụng tìm nghiệm của một phương trình, một bài toán hay gặp trong thực tế.

Tại sao lại cần dùng hàm?

Chương vừa rồi đã giải thích một số ưu điểm của hàm, bao gồm

  • Mỗi hàm có không gian làm việc riêng của nó, vì vậy dùng hàm sẽ tránh được xung đột về tên.
  • Các hàm rất hợp với cách phát triển tăng dần: bạn có thể gỡ lỗi phần thân của hàm trước (dưới dạng tập tin lệnh), rồi gói nó vào trong một hàm, sau đó khái quát hóa bằng cách thêm các biến đầu vào.
  • Hàm cho phép ta chia một vấn đề lớn thành những phần nhỏ để xử lý từng phần một, rồi lắp ghép trở lại thành lời giải hoàn chỉnh.
  • Một khi đã có hàm chạy được, bạn có thể quên đi những chi tiết về cách hoạt động của nó, mà chỉ cần biết nó làm gì. Quá trình trừu tượng hóa này là một cách thức quan trọng để ta quản lý được sự phức tạp của những chương trình lớn.

Một lý do khác khiến bạn phải cân nhắc việc dùng hàm là nhiều công cụ quan trọng của MATLAB yêu cầu bạn phải viết hàm. Chẳng hạn, ở chương này ta sẽ dùng

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 để tìm nghiệm của phương trình phi tuyến. Sau đó ta sẽ dùng

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

0 để tìm nghiệm xấp xỉ của các phương trình vi phân.

Ánh xạ

Trong toán học, ánh xạ là sự tương ứng giữa một tập hợp gọi là tập nguồn và một tập hợp khác được gọi là tập đích. Với mỗi phần tử của tập nguồn, phép ánh xạ sẽ chỉ ra phần tử tương ứng của tập đích.

Bạn có thể tưởng tượng một dãy như là ánh xạ từ tập các số nguyên dương đến tập các phần tử của dãy đó. Bạn có thể tưởng tượng véc-tơ như một ánh xạ từ tập các chỉ số đến các phần tử. Trong các trường hợp này, những ánh xạ là rời rạc vì các phần tử trong tập nguồn là đếm được.

Bạn cũng có thể tưởng tượng một hàm như một ánh xạ từ số liệu đầu vào đến số liệu đầu ra, nhưng trong trường hợp này tập nguồn là liên tục vì số liệu đầu vào có thể nhận bất kì giá trị nào chứ không riêng gì các số nguyên. (Chặt chẽ mà nói, tập hợp của các số có dấu phẩy động là rời rạc, nhưng vì các số dấu phẩy động nhằm biểu diễn cho các số thực, nên ta hiểu rằng chúng liên tục.)

Nói thêm về cách kí hiệu

Trong chương này, tôi bắt đầu nói về các hàm toán học, và tôi sẽ dùng dạng kí hiệu mà có thể chưa gặp bao giờ.

Nếu bạn đã học đến hàm qua môn toán, bạn có thể thấy kí hiệu như sau

f(x) = x2 – 2x – 3

với ý nghĩa rằng f là một hàm chiếu từ x đến x2 – 2x – 3. Vấn đề là f(x) cũng được dùng để chỉ giá trị của f tương ứng với một giá trị của x. Vì vậy, tôi không thích các kí hiệu này. Tôi ưa dùng kí hiệu sau hơn:

f: x → x2 – 2x – 3

với ý nghĩa rằng “f là hàm chiếu từ x đến x2 – 2x – 3.” Trong MATLAB, điều này được diễn đạt bởi:

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

Tôi sẽ sớm giải thích lý do tại sao hàm này được gọi là

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

1. Bây giờ, ta hãy quay trở lại việc lập trình.

Phương trình phi tuyến

Việc “giải” phương trình có nghĩa là gì? Điều này dường như quá rõ ràng, nhưng tôi muốn chúng ta dành một phút để nghĩ về nó, bắt đầu với một câu hỏi đơn giản: giả sử ta muốn biết giá trị của một biến, x, nhưng tất cả những gì ta biết về nó chỉ là một hệ thức x2 = a.

Nếu đã học môn đại số, chắc bạn biết cách “giải” phương trình này: chỉ cần lấy căn bậc hai của hai vế và ta có x = √a. Sau đó, khi thoải mái vì đã giải xong, bạn chuyển sang bài toán tiếp theo.

Nhưng thực sự bạn đã làm gì? Hệ thức mà bạn rút ra tương đương với hệ thức ở đề bài—chúng có cùng thông tin về x—nhưng tại sao hệ thức thứ hai lại được ưa chuộng hơn thứ nhất?

Có hai lý do sau. Đầu tiên là hệ thứ thứ hai đã “tường minh theo x;” bởi vì chỉ có x ở bên vế trái, ta có thể coi vế phải như là một phương thức dễ dàng để tính x, với giả sử là ta đã biết a.

Lý do còn lại là phương thức tính toán này được viết dưới dạng các phép toán mà ta biết cách thực hiện. Giả sử rằng ta biết cách tính căn bậc hai, ta có thể tính được giá trị của x với giá trị a bất kì.

Khi ta nói về giải phương trình, ý nghĩa thông thường của nó là kiểu như “đi tìm một hệ thức tương đương trong đó một ẩn được viết dưới dạng tường minh.” Trong phạm vi cuốn sách này, một hệ thức như vậy được tôi gọi là nghiệm giải tích, để phân biệt với nghiệm số trị, là thứ mà ta cần tìm trong phần tiếp theo đây.

Để ví dụ cho việc tìm nghiệm số trị, ta hãy xét phương trình x2 - 2x = 3. Bạn có thể giải phương trình này theo cách giải tích, bằng phép phân tích thừa số hay dùng công thức giải phương trình bậc hai, để tìm được hai nghiệm của nó, x = 3 và x = –1. Một cách khác là bạn có thể giải phương trình bằng cách viết x = √(2x + 3).

Phương trình này không tường minh, vì x xuất hiện ở cả 2 vế, vì vậy chưa rõ là bước biến đổi này có ích gì. Nhưng giả như là vì lý do nào đó ta biết có một nghiệm gần với 4, ta có thể lấy x = 4 làm “ước đoán ban đầu,” rồi dùng phương trình x = √(2x + 3) lặp lại nhiều lần để tính những liên tiếp những giá trị xấp xỉ của nghiệm.

Sau đây là điều có thể xảy ra:

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

Sau mỗi lượt lặp,

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

2 đã gần hơn đáp số đúng, và sau 5 lần lặp, sai số tương đối chỉ còn khoảng 0,1%, vốn đã đạt yêu cầu cho mọi mục đích tính toán.

Các kĩ thuật giúp tính ra nghiệm số trị được gọi là phương pháp số. Điều hay ở phương pháp mà tôi vừa trình bày là nó đơn giản, nhưng không phải lúc nào cũng hoạt động được như ở ví dụ trên, và thực tế nó thường không được dùng nhiều. Ta sẽ xem một phương pháp thông dụng hơn ngay sau đây.

Tìm nghiệm

Một phương trình phi tuyến như x2 – 2x = 3 là một khẳng định đẳng thức chỉ đúng với một số ít các giá trị của x và sai với tất cả các giá trị khác. Một giá trị khiến cho đẳng thức đúng được gọi là nghiệm; các giá trị khác không phải nghiệm. Nhưng với bất kì một giá trị không phải nghiệm cho trước, cũng chẳng có dấu hiệu gì cho thấy nó gần hay xa một nghiệm, hay ta có thể tìm nghiệm trong khoảng nào.

Để giải quyết hạn chế này, ta cần viết lại phương trình phi tuyến dưới dạng bài toán tìm nghiệm:

  • Bước đầu tiên là định nghĩa một “hàm sai số” để tính xem một giá trị cho trước của x cách xa nghiệm là bao nhiêu. Ở ví dụ này, hàm sai số là f: x → x2 – 2x – 3 Bất kì giá trị nào của x làm cho f(x) = 0 chính là một nghiệm của phương trình ban đầu.
  • Bước tiếp theo là tìm các giá trị của x làm cho f(x) = 0. Các giá trị này được gọi là nghiệm của phương trình.

Việc tìm nghiệm rất hợp với cách giải số trị vì ta có thể dùng các giá trị của f, được tính từ những giá trị khác nhau của x, để suy luận hợp lý về vị trí cần tìm nghiệm.

Chẳng hạn, nếu ta có thể tìm hai giá trị x1 và x2 sao cho f(x1) > 0 và f(x2) < 0, thì ta có thể chắc rằng có ít nhất một nghiệm nằm giữa x1 và x2 (miễn là f liên tục). Trong trường hợp này, ta sẽ nói rằng x1 và x2 bao một nghiệm.

Sau đây là một hình minh họa cho tình huống nói trên:

Cách tra hướng dẫn của hàm trong matlab

Nếu như đó là tất cả những gì bạn biết về f, thì bạn sẽ tìm nghiệm ở đâu? Nếu bạn nói “điểm chính giữa x1 và x2,” thì xin chúc mừng bạn! Bạn đã tìm ra phương pháp số có tên là phân đôi!

Nếu bạn nói, “tôi sẽ nối hai điểm chấm bằng một đường thẳng rồi tính nghiệm của hàm đường thẳng này,” thì xin chúc mừng bạn! Bạn đã tìm ra phương pháp cát tuyến!

Còn nếu bạn nói, “tôi sẽ tính f tại một điểm thứ ba, và vẽ một đường parabol đi qua ba điểm này, rồi tìm các nghiệm của hàm parabol,” thì… ồ, có lẽ bạn sẽ không nói vậy đâu.

Cuối cùng, nếu bạn nói, “tôi sẽ dùng hàm lập sẵn của MATLAB trong đó kết hợp những đặc điểm hay nhất của một số thuật toán mạnh và hiệu quả,” thì bạn đã sẵn sàng chuyển sang mục tiếp theo.

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 là một hàm lập sẵn của MATLAB trong đó kết hợp các đặc điểm tốt nhất của một vài phương pháp số mạnh và hiệu quả.

Để dùng được

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9, bạn phải định nghĩa một hàm MATLAB để tính hàm sai số suy từ phương trình phi tuyến ban đầu, và bạn phải cung cấp một giá trị ước đoán ban đầu về vị trí nghiệm.

Ta đã thấy một ví dụ của hàm sai số:

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

Bạn có thể gọi

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

1 từ Command Window, và đảm bảo rằng có các nghiệm ở 3 và -1.

ans = 0
error_func(-1) ans = 0

Nhưng hãy giả vờ rằng ta không biết chắc vị trí của các nghiệm; ta chỉ biết rằng một nghiệm ở gần 4. Sau đó ta có thể gọi

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 như sau:

ans = 3.0000  

Được rồi! Ta đã tìm thấy một trong số các nghiệm.

Đối số thứ nhất là một chuôi của hàm vốn là tên của tập tin M để lượng giá hàm sai số. Kí hiệu

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

8 cho phép ta nhắc đến tên hàm mà không gọi nó. Cái hay ở đây là bạn thực sự không trực tiếp gọi

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

1; bạn chỉ báo cho

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 biết nó ở đâu. Đến lượt mình,

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 gọi đến hàm sai số—thật ra là hơn một lần.

Đối số thứ hai là giá trị ước đoán ban đầu. Nếu ta cung cấp một ước đoán khác, thì (đôi khi) ta sẽ nhận được nghiệm khác.

ans = -1  

Mặt khác, nếu biết được hai giá trị bao quanh một nghiệm, bạn có thể cung cấp cả hai giá trị đó:

ans = 3  

Đối số thứ hai thực ra là một véc-tơ chứa hai phần tử. Toán tử ngoặc vuông là một trong số vài cách làm tiện lợi để tạo ra một véc-tơ mới.

Bạn có thể tò mò muốn biết

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 gọi đến hàm sai số bao nhiêu lần, và gọi ở những vị trí nào. Nếu bạn sửa

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

1 sao cho nó hiển thị giá trị của

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

2 mỗi lần được gọi, rồi chạy lại

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 thì bạn sẽ thu được:

x = 2
x = 4
x = 2.75000000000000
x = 3.03708133971292
x = 2.99755211623500
x = 2.99997750209270
x = 3.00000000025200
x = 3.00000000000000
x = 3
x = 3
ans = 3  

Không ngạc nhiên là, nó bắt đầu bằng việc tính f(2) và f(4). Sau mỗi lần lặp, khoảng bao nghiệm đã co ngắn lại;

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 dừng khi khoảng bao quá nhỏ và nghiệm được ước tính chính xác đến 16 chữ số. Nếu không cần đạt độ chính xác đến thế, bạn có thể bảo

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 cho một đáp số thô hơn một cách nhanh chóng (hãy xem lời giải thích cách dùng hàm để biết thêm chi tiết).

Điều gì có thể trục trặc?

Vấn đề thường gặp nhất khi dùng

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 là quên mất dấu

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

8. Trong trường hợp đó, bạn sẽ nhận được:

??? Input argument "x" is undefined.
Error in ==> error_func at 2  
    x  

Đây là thông báo lỗi rất dễ lẫn. Vấn đề là ở chỗ MATLAB coi đối số thứ nhất là một lời gọi hàm, vì vậy nó gọi

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

1 mà không kèm theo đối số nào. Vì

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

1 cần một đối số nên thông báo nói rằng đối số nhập vào là “không xác định,” mặc dù có lẽ sẽ rõ hơn nếu nó nói rõ là bạn chưa cung cấp giá trị cho đối số.

Một vấn đề thường gặp khác là viết một hàm sai số mà không bao giờ gán giá trị cho một biến đầu ra. Nói chung, các hàm nên luôn luôn gán một giá trị cho biến đầu ra, nhưng MATLAB không bắt buộc điều này, vì vậy ta rất dễ quên. Chẳng hạn, nếu bạn viết:

function res = error_func(x)
    y = x^2 - 2*x -3
end  

và sau đó gọi nó từ Command Window:

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

0

Dường như nó đã hoạt động, nhưng đừng bị mắc lừa. Hàm này gán giá trị cho

ans = 3.0000  

2, rồi hiển thị kết quả, nhưng khi hàm kết thúc,

ans = 3.0000  

2 sẽ biến mất cùng với không gian làm việc của hàm. Nếu bạn thử dùng nó với

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9, bạn sẽ nhận được

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

1

Đọc kĩ nó, bạn sẽ thấy đây là một thông báo lỗi khá chi tiết (trừ cụm từ “output argument” (đối số đầu ra) không hợp nghĩa với “output variable” (biến đầu ra) cho lắm).

Bạn có thể đã thấy thông báo lỗi này khi gọi

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

1 từ trình thông dịch, nhưng chỉ khi bạn đã gán kết quả cho một biến:

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

2

Bạn có thể tránh trục trặc này nếu nhớ hai quy tắc sau:

  • Hàm nên luôn gán các giá trị cho các biến đầu ra.
  • Khi gọi một hàm, bạn phải luôn làm điều gì đó với kết quả thu được (gán nó cho một biến hoặc dùng nó như một phần của biểu thức, v.v.).

Khi bạn tự viết các hàm và dùng chúng, rất dễ nảy sinh những lỗi không phát hiện ra. Nhưng khi dùng các hàm bạn viết cùng với các hàm của MATLAB như

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9, bạn phải viết đúng!

Còn một điều nữa có thể sai: nếu bạn cung cấp một khoảng số để làm ước đoán ban đầu mà nó lại không chứa nghiệm nào, bạn sẽ nhận được

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

3

Còn một điều trục trặc nữa có thể xảy ra khi bạn dùng

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9, nhưng điều này có vẻ như ít tại bạn. Có khả năng là

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 không thể tìm được nghiệm.

Nói chung

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 khá mạnh, vì vậy bạn có thể không bao giờ gặp vấn đề khi dùng nó, nhưng nhớ rằng không có bảo đảm gì là

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 sẽ chạy, đặc biệt là nếu bạn chỉ cung cấp được một giá trị làm ước đoán ban đầu. Ngay cả khi bạn cung cấp một khoảng bao nghiệm, mọi thứ vẫn có thể trục trặc nếu như hàm sai số bị gián đoạn.

Tìm giá trị ước đoán ban đầu

Giá trị (hay khoảng) ước đoán ban đầu của bạn gần đúng bao nhiêu, thì khả năng hoạt động của

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

9 sẽ cao bấy nhiêu, và càng cần ít lần lặp hơn.

Khi bạn giải bài toán trong thực tế, thường bạn sẽ có sự nhận định về đáp số. Nhận định này thường đảm bảo có được một ước đoán ban đầu gần đúng để tìm nghiệm.

Một cách làm khác là vẽ đồ thị hàm số và xem nếu bạn có thể tìm nghiệm gần đúng bằng mắt thường. Nếu bạn có một hàm, như

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

1 nhận một biến vô hướng và trả lại một biến đầu ra vô hướng, thì bạn có thể vẽ nó bằng

ans = -1  

3:

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

4

Đối số thứ nhất là tên chuôi của hàm; đối số thứ hai là khoảng phạm vi mà bạn muốn vẽ hàm.

Theo mặc định,

ans = -1  

3 gọi hàm của bạn 100 lần (dĩ nhiên là mỗi lần với một giá trị

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

2 khác). Vì vậy bạn có thể muốn làm cho hàm trở nên lặng trước khi vẽ nó.

Nói thêm về xung đột tên

Các hàm và biến chiếm cùng một “không gian tên,” nghĩa là mỗi khi một tên xuất hiện trong biểu thức, MATLAB bắt đầu đi tìm một biến có tên như vậy, và nếu biến đó không tồn tại, thì tìm một hàm.

Kết quả là, nếu bạn có một biến có cùng tên với một hàm thì biến sẽ lấn át hàm. Chẳng hạn, nếu bạn gán một giá trị cho

ans = -1  

6, rồi thử dùng hàm

ans = -1  

6, bạn có thể sẽ nhận được lỗi:

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

5

Trong ví dụ này, vấn đề đã rõ ràng. Vì giá trị của

ans = -1  

6 là một số vô hướng, mà số vô hướng thực ra là ma trận 1×1, MATLAB sẽ cố gắng truy cập phần tử thứ 5 của ma trận và thấy rằng không có phần tử nào như vậy. Dĩ nhiên là nếu “lời gọi hàm” này đặt cách xa lệnh gán thì thông báo lỗi này còn có thể làm ta bối rối hơn nữa.

Nhưng điều duy nhất còn tệ hơn cả nhận thông báo lỗi là không nhận được thông báo lỗi. Nếu giá trị của

ans = -1  

6 là một véc-tơ, hoặc nếu giá trị của

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

2 đã nhỏ hơn thì bạn gặp rắc rối thực sự.

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

6

Hãy xem, sin của 1 đâu có bằng 3 !

Lỗi ngược lại cũng có thể xuất hiện nếu bạn thử truy cập một biến không xác định mà nó tình cờ trùng tên với một hàm. Chẳng hạn, nếu bạn đã có một hàm tên là

ans = 3  

1, và bây giờ thử tăng một biến cùng tên

ans = 3  

1 (trước đó biến này quên không được khởi tạo), bạn sẽ thấy:

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

7

Ít ra đó là những gì bạn thu được nếu may mắn. Nếu điều này xảy ra bên trong một hàm, MATLAB sẽ cố gọi

ans = 3  

1 như một hàm, và bạn thu được

x = 3.3166
x = sqrt(2*x+3)  
x = 3.1037
x = sqrt(2*x+3)  
x = 3.0344
x = sqrt(2*x+3)  
x = 3.0114
x = sqrt(2*x+3)  
x = 3.0038  

8

Không có một cách làm chung để tránh tất cả những lỗi xung đột kiểu này, nhưng bạn có thể giảm khả năng xảy ra lỗi bằng cách chọn các tên biến không trùng với các hàm có sẵn, và bằng cách chọn tên các hàm mà chúng ít có khả năng bị lấy làm tên biến. Đó là lý do tại sao trong Mục Kí hiệu tôi gọi hàm sai số là

function res = error_func(x)
    res = x^2 - 2*x -3;
end  

1 thay vì

ans = 3  

1. Tôi thường đặt tên các hàm kết thúc với

ans = 3  

6, và điều này cũng giúp ích.

Gỡ lỗi bằng bốn hành động

Khi gỡ lỗi một chương trình, đặc biệt nếu bạn phải đương đầu với một lỗi khó, có bốn việc mà bạn cần thử làm:

đọc: Kiểm tra mã lệnh, tự đọc nhẩm, và kiểm tra xem có đúng là chương trình này có ý định thực hiện đúng điều bạn muốn không. chạy: Thử nghiệm bằng cách sửa đổi và chạy các phiên bản mã lệnh khác nhau. Thường nếu bạn hiển thị đúng thứ ở đúng chỗ trong chương trình, vấn đề sẽ trở nên hiểu nhiên, nhưng đôi khi bạn phải dành thời gian để dựng dàn giáo. nghiền ngẫm: Hãy dành thời gian suy nghĩ! Đó là loại lỗi gì: cú pháp, thực thi, logic? Bạn có thể tìm được thông tin gì từ dòng thông báo lỗi, hay từ kết quả đầu ra của chương trình? Loại lỗi gì có thể gây ra vấn đề mà bạn đang thấy? Lần gần nhất bạn đã thay đổi gì ở mã lệnh, trước khi xảy ra lỗi? rút lui: Đến một lúc nào đó, cách tốt nhất là rút lui, hoàn lại những thay đổi mới nhất, đến khi bạn trở về trạng thái của chương trình hoạt động, mà bạn hiểu được. Sau đó bạn có thể bắt đầu lập lại chương trình.

Những người mới lập trình đội khi lún sâu vào một trong những hoạt động này mà quên những hoạt động khác. Mỗi hoạt động có những nhược điểm riêng của nó.

Chẳng hạn, việc đọc mã lệnh có thể giúp ích nếu vấn đề nằm ở lỗi đánh máy, nhưng vô ích nếu vấn đề ở chỗ hiểu sai về khái niệm. Nếu bạn không hiểu chương trình làm gì, thì dù có đọc lại 100 lần bạn cũng chẳng tìm thấy lỗi, vì lỗi nằm ngay trong đầu bạn.

Chạy thử nghiệm có thể giúp ích, đặc biệt với các kiểm tra nhỏ, đơn giản. Nhưng nếu chạy thử mà không nghĩ hoặc đọc mã lệnh thì bạn có thể rơi vào tình trạng mà tôi gọi là “lập trình bước ngẫu nhiên”—một quá trình thực hiện những thay đổi ngẫu nhiên đến khi chương trình hoạt động đúng. Khỏi phải nói, lập trình bước ngẫu nhiên có thể tốn rất nhiều thời gian.

Lối thoát là dành thêm thời gian suy nghĩ. Gỡ lỗi cũng giống như môn khoa học thực nghiệm. Bạn ít nhất phải có được giả thiết về vấn đề. Nếu có nhiều khả năng, hãy cố gắng thực hiện phép thử để loại trừ một trong số các khả năng đó.

Nghỉ ngơi đôi khi cũng giúp ích cho suy nghĩ. Nói chuyện cũng như vậy. Nếu bạn giải thich vấn đề cho ai đó (và ngay cả cho bản thân), bạn đôi khi có thể tìm thấy lời giải ngay trước khi đặt xong câu hỏi.

Nhưng ngay cả những kĩ thuật gỡ lỗi tốt nhất cũng thất bại nếu có quá nhiều lỗi, hay nếu lỗi bạn cố sửa đang quá lớn và phức tạp. Đôi khi lựa chọn hay nhất là rút lui, làm đơn giản chương trình đến khi bạn có được một chương trình hoạt động, rồi sau đó phát triển lại.

Những người mới lập trình thường miễn cưỡng không muốn rút lui, vì họ không thể chịu được nếu phải xóa một dòng lệnh (dù dòng lệnh đó có sai đi nữa). Nếu bạn cảm thấy được, thì hãy sao lưu chương trình vào một tập tin khác trước khi lược bỏ mã lệnh. Sau đó bạn dán lại những mảnh chương trình vào, mỗi lúc một ít.

Tóm lại, sau đây là Định luật thứ chín về gỡ lỗi:

Để tìm ra một lỗi khó, cần phải đọc, chạy thử, suy nghĩ, và đôi khi rút lui. Nếu bạn lún sâu vào một trong những hoạt động này mà không có kết quả, hãy thử hoạt động khác.

Thuật ngữ

nghiệm giải tích: Cách giải phương trình bằng việc thực hiện biến đổi đại số để rút ra một biểu thức tường minh để tính giá trị của một ẩn. nghiệm số trị: Cách giải phương trình bằng việc tìm một giá trị số thỏa mãn phương trình đó, thường chỉ là xấp xỉ. phương pháp số: Phương pháp (hoặc thuật toán) để tính ra nghiệm số trị. ánh xạ: Sự tương ứng giữa các phần tử thuộc một tập hợp (tập nguồn) và các phần tử thuộc tập hợp khác (tập đích). Bạn có thể hình dung các dãy, véc-tơ và hàm như các loại ánh xạ khác nhau. tập nguồn: Tập hợp các giá trị điểm đầu của ánh xạ. tập đích: Tập hợp các giá trị điểm cuối của ánh xạ. tập rời rạc: Một tập hợp, như tập các số nguyên, trong đó các phần tử là đếm được. tập liên tục: Một tập hợp, như tập các số thực, trong đó các phần tử không đếm được. Bạn có thể hình dung tập các số dấu phẩy động như một tập liện tục. nghiệm (của hàm): Giá trị trong tập xác định (tập nguồn) của hàm mà ánh xạ của nó chiếu đến 0. chuôi của hàm: Trong MATLAB, chuôi của hàm là một cách tham chiếu đến hàm bằng một cái tên (và truyền nó dưới dạng một đối số) mà không gọi hàm đó. lấn át: Trường hợp xung đột về tên trong đó một định nghĩa mới khiến cho định nghĩa cũ trở nên không truy cập được. Trong MATLAB, các tên biến có thể lấn át được các hàm lập sẵn (có thể dẫn đến những kết quả rất hài hước).

Bài tập

1. Hãy viết một hàm có tên
fzero(@error_func, [2,4])

ans = 3

7 để lượng giá đa thức Chebyshev bậc 6. Hàm cần nhận một biến đầu vào, x, và trả lại 32x6 – 48x4 + 18x2 – 1

  1. Hãy dùng
    fzero(@error_func, -2)

    ans = -1

    3 để hiển thị một đồ thị của hàm này trong khoảng từ 0 đến 1. Hãy tìm các nghiệm trong khoảng này.
  2. Hãy dùng

    x = 4; x = sqrt(2*x+3) x = 3.3166 x = sqrt(2*x+3) x = 3.1037 x = sqrt(2*x+3) x = 3.0344 x = sqrt(2*x+3) x = 3.0114 x = sqrt(2*x+3) x = 3.0038

    9 để tìm càng nhiều nghiệm càng tốt. Liệu

    x = 4; x = sqrt(2*x+3) x = 3.3166 x = sqrt(2*x+3) x = 3.1037 x = sqrt(2*x+3) x = 3.0344 x = sqrt(2*x+3) x = 3.0114 x = sqrt(2*x+3) x = 3.0038

    9 có luôn tìm được nghiệm gần sát giá trị ước đoán ban đầu nhất hay không?
Khối lượng riêng của một con vịt, ρ, là 0,3 g/cm3 (bằng 0,3 lần khối lượng riêng của nước).

Thể tích của một khối cầu có bán kính r là .

Nếu khối cầu có bán kính r được nhúng trong nước ngập đến độ sâu d, thì thể tích của khối cầu phần bị ngập là

volume = (π/3) (3r d2 – d3) khi d < 2r

Một vật thể luôn nổi lơ lửng ở độ cao sao cho trọng lượng phần bị chìm trong nước đúng bằng trọng lượng của vật ban đầu.

Giả sử rằng con vịt có hình dáng tương đương một quả cầu bán kính 10 cm, phần nhúng nước của con vịt sẽ sâu bao nhiêu?