Chương 6: Tìm nghiệm

Trở về Mục lục cuốn sách

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 fzero để tìm nghiệm của phương trình phi tuyến. Sau đó ta sẽ dùng ode45 để 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à error_func. 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 = 3x = –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 = √(2+ 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 = √(2+ 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 = 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

Sau mỗi lượt lặp, x đã 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ị x1x2 sao cho f(x1) > 0f(x2) < 0, thì ta có thể chắc rằng có ít nhất một nghiệm nằm giữa x1x2 (miễn là f liên tục). Trong trường hợp này, ta sẽ nói rằng x1x2 bao một nghiệm.

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

Hình: secant

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 x1x2,” 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.

fzero

fzero 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 fzero, 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 error_func từ Command Window, và đảm bảo rằng có các nghiệm ở 3 và -1.

>> error_func(3)
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 fzero như sau:

>> fzero(@error_func, 4)
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 @ 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 error_func; bạn chỉ báo cho fzero biết nó ở đâu. Đến lượt mình, fzero 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.

>> fzero(@error_func, -2)
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ị đó:

>> fzero(@error_func, [2,4])

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 fzero 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 error_func sao cho nó hiển thị giá trị của x mỗi lần được gọi, rồi chạy lại fzero thì bạn sẽ thu được:

>> fzero(@error_func, [2,4])

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)f(4). Sau mỗi lần lặp, khoảng bao nghiệm đã co ngắn lại; fzero 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 fzero 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 fzero là quên mất dấu @. Trong trường hợp đó, bạn sẽ nhận được:

>> fzero(error_func, [2,4])

??? 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 error_func mà không kèm theo đối số nào. Vì error_func 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:

>> error_func(4)

y = 5

Dường như nó đã hoạt động, nhưng đừng bị mắc lừa. Hàm này gán giá trị cho y, rồi hiển thị kết quả, nhưng khi hàm kết thúc, y 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 fzero, bạn sẽ nhận được

>> fzero(@error_func, [2,4])

y = -3

??? Error using ==> fzero

FZERO cannot continue because user supplied function_handle ==> 

error_func failed with the error below.

Output argument "res" (and maybe others) not assigned during 

call to "/home/downey/error_func.m (error_func)".

Đọ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 error_func từ trình thông dịch, nhưng chỉ khi bạn đã gán kết quả cho một biến:

>> x = error_func(4)
y = 5

??? Output argument "res" (and maybe others) not assigned during 

call to "/home/downey/error_func.m (error_func)".

Error in ==> error_func at 2

    y = x^2 - 2*x -3

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 ra1.
  • 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ư fzero, 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

>> fzero(@error_func, [0,1])

??? Error using ==> fzero

The function values at the interval endpoints must differ in sign.

Còn một điều trục trặc nữa có thể xảy ra khi bạn dùng fzero, nhưng điều này có vẻ như ít tại bạn. Có khả năng là fzero không thể tìm được nghiệm.

Nói chung fzero 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à fzero 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 fzero 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ư error_func 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 ezplot:

>> ezplot(@error_func, [-2,5])

Đố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, ezplot 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ị x 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 sin, rồi thử dùng hàm sin, bạn có thể sẽ nhận được lỗi:

>> sin = 3;

>> x = 5;

>> sin(x)

??? Index exceeds matrix dimensions.

Trong ví dụ này, vấn đề đã rõ ràng. Vì giá trị của sin 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 sin là một véc-tơ, hoặc nếu giá trị của x đã nhỏ hơn thì bạn gặp rắc rối thực sự.

>> sin = 3;

>> sin(1)
ans = 3

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à f, và bây giờ thử tăng một biến cùng tên f (trước đó biến này quên không được khởi tạo), bạn sẽ thấy:

>> f = f+1

??? Error:  "f" previously appeared to be used as a function or 

command, conflicting with its use here as the name of a variable.

 A possible cause of this error is that you forgot to initialize the

 variable, or you have initialized it implicitly using load or eval.

Í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 f như một hàm, và bạn thu được

??? Input argument "x" is undefined.

Error in ==> f at 3

y = x^2 - a

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à error_func thay vì f. Tôi thường đặt tên các hàm kết thúc với func, 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 cheby6 để 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

  2. Hãy dùng ezplot để 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.
  3. Hãy dùng fzero để tìm càng nhiều nghiệm càng tốt. Liệu fzero 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ầu2 có bán kính r\frac{4}{3} \pi r^3.

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 d2d3)    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?

Sau đây là một số gợi ý để bạn giải bài này:

  • Hãy viết một phương trình liên hệ giữa ρ, dr.
  • Sắp xếp lại phương trình sao cho vế phải là số không. Mục tiêu của chúng ta là tìm những giá trị của d là nghiệm phương trình này.
  • Hãy viết một hàm MATLAB để lượng giá hàm đã thành lập. Hãy kiểm tra nó, rồi sửa nó thành một hàm lặng.
  • Hãy dự đoán giá trị của d0 để làm ước tính ban đầu.
  • Dùng fzero để tìm ra nghiệm gần d0.
  • Kiểm tra để bảo đảm rằng kết quả có ý nghĩa. Đặc biệt, kiểm tra điều kiện d < 2r, bởi nếu không thì công thức thể tích sẽ sai!
  • Thử các giá trị khác nhau của ρr và xem hiện tượng có diễn ra như bạn mong đợi hay không. Điều gì sẽ xảy ra khi ρ tăng? Lên đến vô cùng? Hạ xuống bằng 0 ? Điều gì sẽ xảy ra khi r tăng? Lên đến vô cùng? Hạ xuống bằng không?

  1. Ừ, công nhận là vẫn có ngoại lệ, như find_triples. Các hàm không trả lại giá trị đôi khi vẫn được gọi là “lệnh,” vì chúng làm việc gì đó (như hiển thị giá trị hoặc vẽ hình) nhưng hoặc là không có biến đầu ra hay không gán giá trị cho nó.
  2. Ví dụ này được chính sửa từ Gerald and Wheatley, Applied Numerical Analysis, Fourth Edition, Addison-Wesley, 1989.
Advertisements

3 phản hồi

Filed under Mô hình hóa, Sách

3 responses to “Chương 6: Tìm nghiệm

  1. Pingback: Chương 1. Các biến và giá trị | Blog của Chiến

  2. Pingback: Chương 11: Tối ưu hóa và nội suy | Blog của Chiến

  3. Pingback: Mô hình hóa hiện tượng vật lý bằng MATLAB | Blog của Chiến

Trả lời

Mời bạn điền thông tin vào ô dưới đây hoặc kích vào một biểu tượng để đăng nhập:

WordPress.com Logo

Bạn đang bình luận bằng tài khoản WordPress.com Đăng xuất / Thay đổi )

Twitter picture

Bạn đang bình luận bằng tài khoản Twitter Đăng xuất / Thay đổi )

Facebook photo

Bạn đang bình luận bằng tài khoản Facebook Đăng xuất / Thay đổi )

Google+ photo

Bạn đang bình luận bằng tài khoản Google+ Đăng xuất / Thay đổi )

Connecting to %s