Лекция Изключения. Уравнение на КортевегДеВриз



Дата22.07.2016
Размер120.94 Kb.
#1204
ТипЛекция




Лекция 5. Изключения. Уравнение на КортевегДеВриз.

Програмите трябва да бъдат устойчиви, в смисъл че ако възникне неочаквана ситуация програмата трябва да реагира разумно ( да не зависне или да не произвежда безмислени резултати). Когато възникне такава ситуация наречена изключение (exception) и програмата не може да продължи да се изпълнява безупречно трябва да съществува код, който да указва какво да се прави. Различни грешки могат да предизвикат изключения, примерно наличието на бъг в програмата, грешка във входния поток или даже повреда в твърдия диск. Задължение на програмиста e да подготви програмата за такива неочаквани събития и да напише код за излизане от ситуацията.

Изключенията в Java

Нека да разгледаме следния код:





Програмата изчислява квадрата на цяло число зададено от потребителя чрез клавиатурата. На фиг.1 са показани изходите на програмата в три случая на задаване на числа от клавиатурата.

Enter a number: 56

The square is: 3136


Enter a number: 2.5

Exception in thread “main” java.lang.NumberFormatException:

at java.lang.Intteger.parseInt(Integer.java:423)

at java.lang.Intteger.parseInt(Integer.java:463)

at Sqare.main(Square.java:8)
Enter a number:

Exception in thread “main” java.lang.NumberFormatException:

at java.lang.Intteger.parseInt(Integer.java:435)

at java.lang.Intteger.parseInt(Integer.java:463)

at Sqare.main(Square.java:8)

фиг.1


В първият случай потребителят задава валидно цяло число, във втория случай потребителят въвежда невалидно число(не е цяло), a в третия случай не въвежда нищо. Във вторият и третият случай се генерира изключение и резултат не се извежда на екрана. Генерират се т.нар. изключения по подразбиране, които не винаги са най-добрият завършък на програмата. За по-добър завършък на програмата, когато някое условие се наруши, трябва да се напише код, който да обработва изключенията по различен начин от начина по подразбиране.

Класове на изключения

В Java съществуват много класове на изключения, които покриват различни случай на нарушаване на очакваните условия. Всички класове на изключения са подкласове на основния клас java.lang.Exception. Методът от горния пример има следната декларация:



т.е. обработват се, по подразбиране, грешки във формата на въведеното число.

Иерархиата на класовете на изключения е показана на фиг.2


фиг.2

Хвърляне на изключения

При откриване на грешка в метода, който хвърля изключение, се създава обект на съответния клас на изключение, който по подразбиране извежда информация за грешката и изкарва програмата от метода без да го завърши. Ако искаме методът да бъде завършен с някакво действие трябва да напишем алтернативно изключение.

Нека да си представим че трябва да напишем алтернативен метод Integer.parseInt(), който чете число от клавиатурата и гo извежда на екрана. Рамката на метода може да изглежда по следния начин:

public static int parseInt(String s){

int num = 0;

//проверете дали първия знак е минус и го запомнете като булева променлива

//сканирайте String s назад, знак след знак като проверявате дали всеки знак е //цифра. Повдигнете всяка цифра на степен (според мястото на цифрата) и добавете //към числото.Ако е необходимо променете знака на числото.

return num; }

Как би реагирал метода parseInt() ако някои символ от низа не е число? Имаме различни евентуални възможности.


  • Може да изведем съобщение за грешка. Проблемът с този вариант е че методът не връща никаква стойност.

  • Може да дадем индикация за грешка като върнем някое определено число, примерно -1 или 999. Проблемът тук е че това число може реално да е използвано в низа.

  • Може да излезем от метода с помощта на командата System.exit( ) преди да се стигне до командата return. Проблемът е че цялата програма ще спре.

Най-добре е да завършим метода предварително като предупредим ползователя на класа че нещо не е наред. Такъв механизъм е възможен с помощта на изключенията. Един примерен код, който е по подразбиране, е следния:

Можем да отбележим следното:



  • Изразът throw създава обект на класа NumberFormatException. Този обект съдържа информация за изключението, включително типа и състоянието на програмата в момента в който възниква грешката.

  • След като се изпълни командата throw методът приключва предварително. Така изключенията позволяват да се излезе от метода без да се изпълнят останалите команди.

  • Изразът в заглавието на метода throws NumberFormatException подсказва на потребителя какъв конкретен вид грешка може да възникне.

В заключение, когато пишем класове трябва да предвидим възможните типични грешки и да генерираме (да хвърлим) съответния тип изключение. Ползователят на класа може да реши дали да затвори програмата, дали да поправи грешката или да продължи без да предприеме нищо. Така решението се взима не от човека който пише програмата а от човека който ползва програмата.

В Java има два вида изключения:

Едните са т. нар хвърлени изключения. Това са изключения в реално време (runtime exceptions) и те само се “хвърлят” т.е. генерира се предупреждение. Те са преки или косвени подкласове на класа RuntimeException (виж иерархията на фиг.2). Хвърлените изключения само генерират предупреждение, без да решат проблема.

Другият вид изключения са т. нар. хванати изключения. Те се проверяват от компилатора и се “хващат”, за да се обработят, или да се конкретизират, така нареченото правило “catch or specify.”



Обработване на изключения

Досега разглеждахме само изключения по подразбиране т.е. хвърлени изключения, които извеждат техническо съобщение и напускат програмата. Сега ще разгледаме как се хващат и обработват изключения.

Нека в началото да разгледаме следния метод average( ), който хвърля изключения, но не ги лови и не ги обработва. Този метод е дефиниран в класа EssentialMath както следва:

import java.io.*;

import java.util.*;

class EssentialMath{

public static double average() throws IOException{

System.out.println("Enter numbers at the same line");

BufferedReader b=new BufferedReader(new InputStreamReader(System.in));

String s=b.readLine();

StringTokenizer st=new StringTokenizer(s);

double sum=0;

int numberOfValues =0;

while(st.hasMoreTokens()){

String n=st.nextToken();

double num=Double.parseDouble(n);

sum +=num;

numberOfValues++;

}

double average=sum/numberOfValues;



return average;

}

}



public class FindAverage {

public static void main(String[] args)throws IOException {

double avg=EssentialMath.average();

System.out.println("The average value is: "+avg);

}

}

Методът average( ) вика два метода, които могат да хвърлят изключения:



  • Методът readLine от класа BufferedReader, който хвърля изключение от тип IOException.

  • Методът Double.parseDouble, който хвърля изключение от типа NumberFormatException.

Отбележете че ние определяме типа на изключението IOException, което е проверено т.е. хванато изключение. Същевременно не определяме типа на изключението NumberFormatException, което е изключение в реално време т.е. хвърлено изключение.

Хващане на изключение

Ако стартираме горната програма, тя очаква някякви входни числа за да определи тяхната средна стойност. Ако обаче въведем неправилно число ще се генерира изключение в метода parseDouble(). В резултат ще се изведе съобщение за грешка и програмата ще приключи. Сега искаме да хванем това изключение и да го обработим така че програмат да пренебрегне грешните входни данни и да изведе краен резултат. По-долу е показан модифициран код, който хваща изключение от вида NumberFormatException.

public static double average() throws IOException{

System.out.println("Enter numbers at the same line");

BufferedReader b=new BufferedReader(new InputStreamReader(System.in));

String s=b.readLine();

StringTokenizer st=new StringTokenizer(s);

double sum=0;

int numberOfValues =0;

int numberOfErrors=0;

while(st.hasMoreTokens()){

String n=st.nextToken();

try{

double num=Double.parseDouble(n);

sum +=num;

numberOfValues++;

}catch(NumberFormatException e){

numberOfErrors++;

System.out.println("Invalid number at position "+

(numberOfValues+numberOfErrors)+" ignored.");

}

}

double average=sum/numberOfValues;

return average;

}

Ако на входа подадем следните данни:



на изхода ще получим:



Сега ще дискутирами горния код. Първо затваряме в блока try{… } кода, който потенциално може да генерира изключение. Изключението се улавя и обработва в блока catch{…}. Основната структура на двата блока изглежда по следния начин:



Командата catch прилича на метод, с един формален аргумент, като типа на аргумента указва какво изключение е хванато. Двете структури try и catch са винаги заедно и между тях не може да има друг код. Смисълът на горният код е следният:



  • Ако първата команда в блока try генерира изключение, следващите редове не се изпълняват и се минава към командите в блока catch. Обратно, ако първата команда в try не генерира изключение се изпълняват следващите два реда, а блока catch се пренебрегва.

  • Записва се номера на невалидните числа така че ползователя се информира кои входни данни са пренебрегнати поради невалидност.

  • Ако изключението NumberFormatException се проверява от try и catch тогава не е необходимо да използваме throws в методите average и main.

Какво става когато се хвърли (генерира) изключение?

Кодът в блока catch се нарича обработка на изключението. Задачата на този блок е програмата да се възстанови от възникналия проблем и да продължи. Понеже всяко изключение е определен тип (задаван от формалният аргумент), то кодът съответства на конкретното изключение. Възможно е в един try блок да се хвърлят повече от едно изключение, които се обработват от съответния брой catch блокове, както е показано по-долу:





фиг.3


Ако се генерира изключение някъде в метода се проверяват всички налични catch блокове докато се намери съвпадение на типа изключение. Ако не се намери съвпадение изключението се връща на викащия метод, след това на другия викащ метод и т.н. по викащата верига до главния метод. Ако никъде не се открие съвпадение изключението се връща на интерпретатора, който по подразбиране дава съобщение за грешка и приключва програмата. Това е показано графично на фиг.3
Уравнение на Кортевег-де Вриз (КДВ). Солитони.

Досега разглеждахме линейни частни диференциални уравнения. При обикновените линейни диференциални уравнения намирането на аналитично решение се улеснява от принципа на суперпозиция, според който сумата от две решения също е решение. Когато включим в уравнението ефекти от по-висок порядък, т.е. нелинейни ефекти, могат да възникнат решения с необикновено поведение, както ще видим по-късно. Численото решаване на нелинейни уравнения не е по-трудно от численото решаване на линейни уравнения за разлика от аналитичното решаване, което е много по-трудно за нелинейните уравнения.

Нашата задача е да установим дали нелинейни и дисперсни системи могат да поддържат вълни със свойства на частици. Интуитивният отговор е, че дисперсията би довела до размиване на формите с времето, и амплитудите намаляват докато те загубят своята идентичност. Обратно, нелинейноста изостря формите, а амплитудите нарастват. Въпросът е какво става ако двата ефекта с противоположно действие се изравнят.

През 1834 г. шотландецът Ръсел наблюдавал как една лодка била теглена в тесен канал от два коня по брега и когато лодката внезапно спряла, увлечената от лодката водна маса продължила по инерция своето движение във вид на единично възвишение, което се разпространявало запазвайки формата и скороста си. Ръсел силно се заинтересувал от това явление и успял да го повтори в лабораторни условия като емпирично открил следната връзка между скороста с, амплитудата А, дълбочината на канала h и земното ускорение g:



c2=g(h+A) (1)

Уравнение (1) разкрива ефект, който не се наблюдава в линейни системи, а именно че скороста зависи от амплитудата. При това вълни с по-голяма амплитуда имат по-голяма скорост на разпространение от вълните с по-малка амплитуда. Това свойство е различно от дисперсията, при която вълновата скорост зависи от честотата, съответно от дължината на вълната. Ръсел също забелязал, че когато в канала се възбуди произволна вълна тя преминава в две или повече вълни които, движейки се с различни скорости се разделят в отделни и самостоятелни възвишения, наречени по-късно солитони. Тези солитони са нормалните моди на нелинейната система.

За да разберем тези необикновени водни вълни, които възникват в тесни и плитки канали ще разгледаме следното частно диференциално уравнение:

(2)

Може да се отгатне основната физика в горното уравнение. В (2) имаме нелинейния член εuu/∂x докато в обикновеното вълново уравнение имаме члена cu/∂x. Това означава, че когато амплитудата u не се променя много вълната се разпространява със скорост пропорционална на εu т.е. скороста е пропорционална на амплитудата. Така тези части на вълната, които имат по-голяма амплитуда u се движат по-бързо. Това може да доведе до заостряне (свиване) на вълната и евентуално до ударна вълна.

Обратно на свиването на вълната членът 3u/∂x3 в (2) предизвиква дисперсно разширяване на формата на вълната. Когато, при определени условия, нелинейното свиване и нарастване на амплитудата точно компенсира дисперсното разширяване и затихване, възниква вълна с постоянна форма и амплитуда наречена солитон.

Преди да се опитаме да решим уравнението КДВ е полезно да изследваме поведението на уравнението в различни гранични случаи когато доминира един или друг член. Започваме с линейното вълново уравнение:



(3)

Уравнение от този вид има решение във вид на разпространяващи се плоски вълни от вида:



(4)

Когато решението (4) се замести в (3) получаваме следната връзка между скорост и честота:



(5)

Връзката (5) е валидна за случай без дисперсия тъй като скороста не зависи от честотата.

Нека сега въведем дисперсия, такава че честотата намалява когато вълновото число к нараства:

(6)

Във връзката (6) са взети само четни степени на к, което отразява симетрията спрямо посоките ±х. Уравнението (6) има следните две решения спрямо честотата:



(7)

Тъй като при решения от вида на плоска вълна (4) първите степени по ω възникват от първа производна по времето, а първите степени по к възникват от първа производна по координатата, то за да получим закон на дисперсията от вида (7) вълновото уравнение трябва да е от следния вид:


(8)

Това уравнение вече е близко до уравнението КДВ.

Сега нека да пренебрегнем дисперсионния член в уравнението КДВ. Получаваме следното уравнение на Бургер:

(9)

За малки стойности на u уравнение (9) е почти линейно уравнение. В общия случай, обаче, това е нелинейно уравнение, което описва възникването на ударна вълна. Такова поведение е показано на фиг.4 при ε=1. Анализът показва че нелинейния член в уравнението КДВ изразява възможноста за възникване на ударна вълна в решението.


Фиг.4 Образуване на ударна вълна



ЧИСЛОВ ПОДХОД

Уравнението КДВ може да се реши числово като се използва представяне на производните чрез схема на централни крайни разлики както следва:



(10)

Решаваме уравнението върху двумерна решетка на координатата и времето:



x=iΔx, t=jΔt (11)

След обичайното развите в ред на Тейлор на функциите u(x, t+Δt) и u(x, t-Δt) получаваме следната оценка за производната по времето върху решетката (i, j):

(12)

Аналогично за производната по х имаме:

(13)

За да намерим числово приближение за третата производна по х ще развием u(x,t) в ред около четирите точки u(x±2Δх,t) и u(x±Δх,t). Типично разложение е следното:



(14)

Може да се покаже че третата производна се апроксимира по следния начин:



(15)

Накрая функцията u(х,t) във втория член на уравнението КДВ се апроксимира чрез усредняване по три точки както следва:



(16)

След заместване получаваме следния алгоритъм за числово решаване на уравнението КДВ:



(17)

За да приложим алгоритъма трябва да знаем u(х,t) в настоящия момент и една стъпка в миналото за да изчислим една стъпка в бъдещето. Началното условие u(i,1) лесно се определя понеже u(х,t=0) е известна начална функция от х. За да определим u(i,2) ще използваме нецентрална схема за диференциране напред т.е вместо (16) ще използваме:

(18)

Получаваме следната модификация на (17):



(19)

Трябва също така да определим граничните условия т.е. първите две начални колони u(1,j) u(2,j) и последните две крайни колони u(Nmax,j) u(Nmax-1,j) в решетката. Можем да предположим че u(1,2)=1 ; u(Nmax,2)=0; u(i+2,2)= u(i+1,2);

u(i-2,2)= u(i-1,2). За да проведем тези стъпки в (17) ще използваме следното приближение:

Условието за стабилност на този алгоритъм е следното:



(20)

От условието за стабилност се вижда, че когато намаляваме пространствената стъпка, или даже когато намаляваме едновременно и пространствената и времевата стъпка, се доближаваме до нестабилност. В нелинейните задачи са необходими специално внимание и експериментиране при избиране на стъпките.



Зад.1 Напишете програма за решаване на уравнението КДВ с начално условие-вълна с две нива:

(21)

с параметри ε=0.2 и μ=0.1. Започнете с Δx =0.4 Δt =0.1. Тези константи удовлетворяват условието за стабилност (20) при |u|=1.

Упътване към програмата:


  • Дефинирайте двумерен масив u(131,3) с първи индекс за положението х и втори индекс за времето t.

  • Инициализирайте амплитудите u(i,1) като използвате (21).

  • Изчислете u(i,2) като използвате (19). Забележете, че не можете да започнете с i=1 или да завършите с i=131 понеже ще излезете от границите на масива.

  • За да определим първите две стъпки във времето (алгоритъма започва да работи от третата стъпка) приемаме u(1,2)=1 и u(131,2)=0. За да определим u(2,2) и u(130,2) приемаме че u(i+2,2)= u(i+1,2) и u(i-2,2)= u(i-1,2).

  • Положете u(i,1)= u(i,2) и u(i,2)= u(i,3) за всяко i. Така настоящето става минало и бъдещето става настояще. Така се придвижваме с една стъпка във времето.

  • Повторете итерациите във времето 2000 пъти като записвате резултата във файл след всеки 250 стъпки.

Представете резултата във вид на тримерна графика u(x,t). Наблюдавайте как еволюира профила на вълната във времето и проверете дали е верно заключението на Ръсел, че по-високите солитони пътуват по-бързо от

по-ниските солитони.



Фиг. 5 Превръщане на вълна с две нива в поредица от 8 солитона. Солитонът

1,с най-голяма амплитуда, е най-тясен и се движи най-бързо надясно.

Зад. 2 Изследване пресичането на два солитона.

Изследвайте какво ще се случи когато един висок солитон се удря с един нисък солитон като отговорите на следните въпроси. Ще се отразят ли след удара? Ще преминат ли един през друг? Ще интерферират ли? Ще се разрушат ли взаимно? Ще продължи ли по-високия да се движи по-бързо след удара, така както е било преди удара?

Използвайте следното начално условие:

(22)

По-високия солитон с амплитуда 0.8 е разположен при х=12, а по-ниския с амплитуда 0.3 е пред него при х=26.

Параметрите имат следните стойности: ε=0.2 и μ=0.1. Започнете с Δx =0.4 Δt =0.1. Повторете итерациите във времето 4000 пъти като записвате резултатите след всеки 400 итерации.

Фиг. 6 Пресичане на два солитона без да си взаимодействат.


Зад.3 Изследвайте уравнение (9) и покажете че то може да произведе ударна вълна.
Каталог: ~tank -> JAVA -> Lectures
Lectures -> Лекция 2 Типове данни. Класове и обекти. Числено интегриране
Lectures -> Програма за изчисляване на средна стойност
Lectures -> Лекция аплети и уеб-страници
Lectures -> Лекция Mногонишково програмиране в java понятието нишка (thread) се свързва с контрола или изпълнението на отделна програма или задача. Нишките са опънати във времето и свързват една с друга последователните команди
Lectures -> Програма за изчисляване на средна стойност
Lectures -> Лекция Характерни особености на езика java. Сравнение с други
Lectures -> Приложение Аплети с геометрични фигури. // A java applet displaying a white circle in a black square
Lectures -> Лекция 10. Програмиране в уеб-страница с помощта на Java Script
Lectures -> Лекция Входни и изходни потоци. Писане във файл и четене от


Сподели с приятели:




©obuch.info 2024
отнасят до администрацията

    Начална страница