Построение графиков функций или повторение пройденного с вариациями

Так вот получилось, что недавно пришлось заняться разработкой небольшой библиотеки математических функций, которые или слабо известны или же очень редко упоминаются. Естественно, разработка такой библиотеки довольно непростое занятие (приходиться вспоминать математику или искать разложение в ряды некоторых довольно специфических функций) тем более, что библиотека пишется не для классического Icon, а для его объектно-ориентированной версии (т.е для ObjectIcon).

И вот, засучив рукава, я сделал около 30 различных функций, реализованных в виде статических методов (public static) одного класса под названием ExtMath (Extended Mathematics — расширенная математика).

Класс, конечно, хороший и все считается на ура (проверял на схожих функциях из других языков программирования), но все равно даже затабулировав функции никакой наглядности не получишь…

Исходя из этого, я вспомнил одну интересную идею, которая у меня как-то была реализована для ObjectIcon. Идея была в том, чтобы написать небольшую программку демонстрирующую графики различных функций. Однако, программка писалась на скорую руку и как следствие абсолютно нечитаема и не обладает универсальностью.

Тогда мне пришла в голову мысль — переработать этот код и показать при этом некоторые нюансы ObjectIcon.

Графика в ObjectIcon работает совершенно иначе, чем в Icon. Прежде всего, это связано с тем, что в ObjectIcon библиотеки, ответственные за графику полностью переработаны и перемещены в модуль graphics.

Например, чтобы открыть окно необходимо импортировать модуль graphics и выполнить примерно такую последовательность действий:

w := Window().
   set_size(200,200).
   set_fg("red").
   set_bg("light grey").
   set_font("serif,24").
   erase_area().
   set_canvas("normal") | stop(&why)

т.е необходимо создать объект класса Window (да это объект, а не файл, как в Icon) и
с помощью методов установить нужные для окна параметры (размер, цвет фона, цвет
тона, режим холста и так далее). С этим в принципе все ясно и понятно.
Но вот в чем ложка дегтя… После запуска этой последовательности операций окно
появиться и тут же закроется. Для того, чтобы этого не произошло мы воспользуемся
бесконечным циклом для перехватывания различных событий:

repeat {
    while e:=w.event() do {
    case e[1] of {
        Mouse.LEFT_PRESS : write(e[2],",",e[3])
        Mouse.RIGHT_PRESS : {
            w.set_fg("blue")
            w.draw_string(10,30,"Function: " || read())
         }
        "q" : exit()
      }
    }
}

Также нетрудно заметить, что перехватываемые события значительно отличаются от
тех, что приняты в классическом Icon (это события Mouse.LEFT_PRESS и Mouse.RIGHT_
PRESS). На нажатие левой клавиши мыши завязана выдача текущих координат курсора,
а на правую клавишу — считывание подписи для графика и вывода ее в окно.
Но, вставлять такой цикл разумно только после выполнения программой основной работы, т.е после вычерчивания графика. Для выполнения же этой задачи создается вот такой класс:

class Plot()

public static grid(w)
local i
every i:=0 to 500 by 10 do {
     w.draw_line(i,0,i,500)
     w.draw_line(0,i,500,i)
}
w.set_fg("black")
w.draw_line(250,0,250,500)
w.draw_line(0,250,500,250)
end

public static gplot(w,func)
local i,s,k
i := -250
while i < 250 do {
     s := 150 * func(i/20.0)
     k := 250-s
     gradient(w,s)
     w.draw_point(250+i,k)
     w.draw_line(250+i,k,250+i,250)
     i +:= 1
}
end

public static plot(w,func,col,dx)
local i,s,k
i := -250
/col := "red"
/dx := 0.001
w.set_fg(col)
while i < 250 do {
     s := 150 * func(i/20.0)
     k := 250-s
     w.draw_point(250+i,k)
     i +:= dx
}
end

private static gradient(w,s)
if s>=200 then w.set_fg("black") else {
   if 150<=s<200 then w.set_fg("violet") else {
     if 100<=s<150 then w.set_fg("blue") else {
       if 50<=s<100 then w.set_fg("light blue") else {
         if 0<=s<50 then w.set_fg("cyan") else {
          if -50<=s<0 then w.set_fg("green") else {
           if -100<=s< -50 then w.set_fg("yellow") else {
            if -150<=s< -100 then w.set_fg("orange") else {
              if -200<=s< -150 then w.set_fg("pink") else {
                if s< -201 then w.set_fg("red")
                }
              }
            }
          }
        }
      }
    }
  }
}
end
end

Теперь, небольшие пояснения по работе этого класса. Метод grid построит координатную сетку и выделит оси, идущие из начала координат. Как и другие методы класса, он требует в качестве обязательного аргумента переменную, содержащую объект класcа Window.

Метод plot требует несколько аргументов: окно для отрисовки, функция для отрисовки графиков, цвет графика и «разрешающая способность» графика, причем цвет и «разрешающая способность» являются необязательными аргументами.

Метод gplot требует два параметра — окно для отрисовки и функцию, но работает этот метод очень интересным образом: процедура раскрашивает площадь под графиком в разные цвета в зависимости от того, какое значение принимает функция в этих точках, т.е происходит так называемое градиентное окрашивание.

Для подобного действия используется приватный метод (т.е внутренний метод класса) gradient, производящий оценку значения и исходя из этого задающий цвет тона.

Ну а напоследок, парочка интересных функций в градиентной окраске.

Обратная функция Гудермана:
arcgd_0_o
Дробная часть числа:
frac_0_o
Функция Гудермана:
gd_0_o
Ненормированный кардинальный синус:
sinc_0_o
Треугольная функция:
tri_0_o
Прямоугольная функция:
rect_0_o
Пила:
saw_0_o
Секанс:
sec_0_o
Синус интегральный:
si_0_o
Не забудьте после строчек импорта поставить invocable all. Размещаю код класса ExtMath, который был использован:

Код класса под спойлером. Кликните для просмотра.

package ext.Math
import 
   util(Math),
   ipl.factors(factorial),
   ipl.math

class ExtMath()
public static const 
   syms,
   eiler

   private static init()
   syms := &digits++&ucase
   eiler := 0.5772156649015328606065120
   end

   public static to_sys(x,n)
      local r,s,i,t,res
      s := list()
      t := list()
      while x >= 1 do {
         r := x%n
         put(s,r)
         x := integer(x/n)
      }
      every i := *s to 1 by -1 do {
         put(t,syms[s[i]+1])
      }
      res:=""
      every i := 1 to *t do {
         res ||:= t[i]
      }
      return res
      end

   public static from_sys(x,n)
      local r,t,i
      r := 0
      x := reverse(x)
      every i := 1 to *x do {
	 if x[i] == !syms then t := find(x[i],syms)-1
         r +:= t*(n^(i-1))	    
      }
      return r
   end

   public static to_sys2(x,n1,n2)
   local tmp
   tmp := from_sys(x,n1)
   return to_sys(tmp,n2)
   end 

   public static root(x,y)
   return x^(1.0/y)
   end

   public static discriminant(a,b,c)
   return (b^2) - 4 * a * c
   end

   public static sgn(x)
   if x < 0 then return -1 else {
     if x = 0 then return 0 else return 1
   } 
   end
 
   public static frac(x)
   return abs(x - integer(x))
   end

   public static floor(x)
   return integer(x - frac(x))
   end

   public static ceil(x)
   if frac(x) = 0 then return x else return 1 + floor(x)
   end

   public static heavyside(x)
   if x < 0 then return 0 else {
      if x = 0 then return 0.5 else return 1
   }
   end

   public static heavyside2(x)
   if x < 0 then return 0 else return 1
   end

   public static rect(x)
   if abs(x) > 0.5 then return 0 else {
      if abs(x) = 0.5 then return 0.5 else return 1
   }
   end

   public static tri(x)
   if abs(x) < 1 then return 1 - abs(x) else return 0
   end

   public static saw(x,a)
   local tmp
   /a := 1
   tmp := x/a
   return tmp - floor(tmp + 0.5)
   end

   public static sinc(x)
   if x = 0 then return 1 else return Math.sin(Math.PI * x)/(Math.PI * x)
   end

   public static sinc2(x)
   if x = 0 then return 1 else return Math.sin(x)/x
   end
   
   public static weierstrass(a,b,x)
   local i,k,m,s
   i:=0
   s:=0
   while i < 10 do {
      k := Math.cos(Math.PI*x*(a^i))
      m := b^i
      s +:= k*m
      i +:= 1
   }
   return s
   end

   public static si(x)
   local i,g,s,k,v
   i := 0
   s := 0
   while i < 20 do {
      k := (2*i)+1
      g := (-1)^i
      v := (g*(x^k))/(factorial(k)*k)
      s +:= v
      i +:= 1
   }
   return s
  end

   public static ci(x)
   local i,s,k
   s := eiler+Math.log(x,Math.E)
   i :=1
   while i<20 do {
      k := (((-1.0)^i)*(x^(2.0*i)))/(factorial(2.0*i)*2.0*i)
      s +:= k
      i +:= 1
   }
   return s
   end  

   public static erf(x)
   local a,b,c,s,i
   a := 2.0 / Math.sqrt(Math.PI)
   s := 0
   every i:=0 to 20 do {
      b := 2*i + 1
      c := ((-1)^i * (x^b)) / (b * factorial(i))
      s +:= c
   }
   return a * s
   end

   public static erfc(x)
   return 1 - erf(x)
   end

   public static gd(x)
   return 2 * Math.atan(Math.exp(x)) - (Math.PI / 2.0)
   end

   public static arcgd(x)
   return 0.5 * Math.log((1.0+Math.sin(x))/(1.0-Math.sin(x)))
   end

   public static sec(x)
   return 1.0 / Math.cos(x)
   end

   public static cosec(x)
   return 1.0 / Math.sin(x)
   end

   public static ctg(x)
   return Math.cos(x) / Math.sin(x)
   end

   public static ei(x)
   local s,i,a
   s := Math.log(x) + eiler
   every i := 1 to 20 do {
      a := (x^i) / (factorial(i)*i)
      s +:= a
   }
   return s 
   end

   public static li(x)
   return ei(Math.log(x))
   end

   public static cth(x)
   return 1.0 / tanh(x)
   end

   public static sech(x)
   return 1.0 / cosh(x)
   end

   public static csch(x)
   return 1.0 / sinh(x)
   end
   
end


P.S: Класс ExtMath скоро попадет в библиотеку функций ObjectIcon.

Добавить комментарий