Skip to content

Commit

Permalink
🔨🔧 feat: Fix bugs, streamline supervisor architecture
Browse files Browse the repository at this point in the history
  • Loading branch information
worthant committed Nov 12, 2024
1 parent d99ccef commit 8242690
Show file tree
Hide file tree
Showing 9 changed files with 236 additions and 644 deletions.
39 changes: 31 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,22 +55,24 @@
> Шаг дискретизации `s` - step
```bash
╭─boris at fedora in ⌁/dev/functional-programming-course/fp-lab3 (main↑1 ●2✚4…5)
╰─λ cat ./resources/large_input_data.csv | ./interpolation_cli -s 1.0 > ./resour
ces/large_output_data.md
╭─boris at fedora in ⌁/dev/functional-programming-course/fp-lab3 (main ✚5…2)
╰─λ cat ./resources/large_input_data.csv
| ./interpolation_cli -a linear,lagrange -s 1.0
> ./resources/large_output_data.md
```

### Ручной ввод

> Шаг дискретизации `s` - step
```bash
╭─boris at fedora in ⌁/dev/functional-programming-course/fp-lab3 (main↑1 ●2✚4…6)
╰─λ ./interpolation_cli -s 1.0 0 (0.409s) < 15:31:52
╭─boris at fedora in ⌁/dev/functional-programming-course/fp-lab3 (main ✚4…2)
╰─λ ./interpolation_cli -a linear,lagrange -s 1.0 130 (37.076s) < 14:21:11
0 0
1.571 1

Linear (going from point 0.0 with step 1.0, covering all input X (1.57 < 2.0)):
Linear (going from point 0.0 with step 1.0, covering all input X (1.57 < 2.0))
:
0.0 1.0 2.0
0.0 0.64 1.27

Expand All @@ -87,6 +89,25 @@ Linear (going from point 3.14 with step 1.0, covering all input X (4.71 < 5.14))
:
3.14 4.14 5.14
0.0 -0.64 -1.27


Lagrange (from point 0.0 with step 1.0, covering all input X (4.71 < 5.0))
:
0.0 1.0 2.0 3.0 4.0 5.0
0.0 0.97 0.84 0.12 -0.67 -1.03

12.568 0

Linear (going from point 4.71 with step 1.0, covering all input X (12.57 < 12.71))
:
4.71 5.71 6.71 7.71 8.71 9.71 10.71 11.71 12.71
-1.0 -0.87 -0.75 -0.62 -0.49 -0.36 -0.24 -0.11 0.02


Lagrange (from point 1.57 with step 1.0, covering all input X (12.57 < 12.57))
:
1.57 2.57 3.57 4.57 5.57 6.57 7.57 8.57 9.57 10.57 11.57 12.57
1.0 0.37 -0.28 -0.91 -1.49 -1.95 -2.26 -2.38 -2.25 -1.84 -1.11 0.0
```

<a id="arch"></a>
Expand Down Expand Up @@ -173,5 +194,7 @@ RabbitMQ и Kafka. Также интересна работа Супервизо
ген серверами, и в случае возникновения любого эксепшена не падает всё
приложение, а продолжает работу в штатном режиме. Стратегий супервизора не так
много и легко изучаются, язык прямо сделан для создания хорошей нагруженной
системы. В то же время, модули для работы с потоками I/O предоставляют
достаточно интуитивный и простой интерфейс для работы.
системы. Поигрался с обработкой сообщений и общением с супервизором. Модули для
работы с потоками I/O предоставляют достаточно интуитивный и простой интерфейс
для работы, поэтому было интересно и не слишком больно работать с потоковыми
режимами.
35 changes: 28 additions & 7 deletions lib/application.ex
Original file line number Diff line number Diff line change
@@ -1,25 +1,46 @@
defmodule InterpolationCli.Application do
@moduledoc """
Главный модуль приложения, запускающий супервизор и инициализирующий процессы
для обработки ввода, интерполяции и вывода данных.
Main application module - inits supervisor and processes for handling i/o and interpolation
"""

use Application

def start(_type, _args) do
# Это нужно для соответствия спецификации Application
{:ok, self()}
end

def start_link(frequency, step) do
# Запускаем супервизор
children = [
def start_link(algo, frequency, step) do
do_nothing_with_frequency(frequency)

# Set algorithms in application environment for access in InputHandler
Application.put_env(:interpolation_cli, :algorithms, algo)

base_children = [
{InterpolationCli.InputHandler, []},
{InterpolationCli.LinearInterpolator, [frequency, step]},
{InterpolationCli.OutputHandler, []}
]

additional_children = []

additional_children =
if :linear in algo,
do: additional_children ++ [{InterpolationCli.LinearInterpolator, step}],
else: additional_children

additional_children =
if :lagrange in algo,
do: additional_children ++ [{InterpolationCli.LagrangeInterpolator, step}],
else: additional_children

# Combine all children
children = Enum.concat(base_children, additional_children)

opts = [strategy: :one_for_one, name: InterpolationCli.Supervisor]
Supervisor.start_link(children, opts)
end

# Dummy function that does nothing but uses frequency
defp do_nothing_with_frequency(_frequency) do
:ok
end
end
58 changes: 39 additions & 19 deletions lib/cli.ex
Original file line number Diff line number Diff line change
Expand Up @@ -4,37 +4,47 @@ defmodule InterpolationCli.CLI do
"""

def main(args) do
Process.flag(:trap_exit, true)

{options, _, _} =
OptionParser.parse(args,
switches: [frequency: :integer, step: :float],
aliases: [f: :frequency, s: :step]
switches: [algorithm: :string, frequency: :integer, step: :float],
aliases: [a: :algorithm, f: :frequency, s: :step]
)

frequency = Keyword.get(options, :frequency, 10)
step = Keyword.get(options, :step, 1.0)

{:ok, _pid} = InterpolationCli.Application.start_link(frequency, step)
algorithms =
case options[:algorithm] do
nil ->
[:linear]

read_input()
end
algos ->
algos
|> String.split(",")
|> Enum.map(&String.trim/1)
|> Enum.map(&String.to_atom/1)
end

defp read_input do
# IO.write("> ")
algorithms = if algorithms == [], do: [:linear], else: algorithms

{:ok, supervisor_pid} = InterpolationCli.Application.start_link(algorithms, frequency, step)

case IO.gets("") do
:eof ->
:ok
read_input()

{:error, reason} ->
IO.puts("Error reading input: #{reason}")
await_shutdown(supervisor_pid)
end

data ->
String.trim(data)
|> String.split(~r/\s+/)
|> parse_line()
defp read_input do
IO.stream(:stdio, :line)
|> Enum.each(fn line ->
String.trim(line)
|> String.split(~r/\s+/)
|> parse_line()
end)

read_input()
end
InterpolationCli.InputHandler.input_finished()
end

defp parse_line([x_str, y_str]) do
Expand All @@ -47,5 +57,15 @@ defmodule InterpolationCli.CLI do
end
end

defp parse_line(_), do: IO.puts("Неверный формат ввода. Ожидается: x y")
defp parse_line(_), do: IO.puts("Invalid input format. Expected: x y")

defp await_shutdown(supervisor_pid) do
receive do
{:EXIT, ^supervisor_pid, _reason} ->
IO.puts("All tasks completed, shutting down.")
after
10 ->
IO.puts("Timeout waiting for processes to complete.")
end
end
end
23 changes: 19 additions & 4 deletions lib/input_handler.ex
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
defmodule InterpolationCli.InputHandler do
@moduledoc """
Модуль для обработки входного потока данных. Сохраняет точки и отправляет их
в модуль интерполяции, как только имеется достаточно данных.
Handles the input stream of data points, storing them and passing them to interpolation as required.
"""

use GenServer
Expand All @@ -15,6 +14,10 @@ defmodule InterpolationCli.InputHandler do
GenServer.cast(__MODULE__, {:add_point, {x, y}})
end

def input_finished do
GenServer.cast(__MODULE__, :input_finished)
end

# Callbacks
@impl true
def init(_) do
Expand All @@ -25,12 +28,24 @@ defmodule InterpolationCli.InputHandler do
def handle_cast({:add_point, point}, state) do
new_state = [point | state] |> Enum.sort_by(fn {x, _y} -> x end)

# Передаём последние две точки для интерполяции
if length(new_state) >= 2 do
algorithms = Application.get_env(:interpolation_cli, :algorithms, [:linear])

if :linear in algorithms and length(new_state) >= 2 do
last_two_points = Enum.take(new_state, -2)
InterpolationCli.LinearInterpolator.interpolate(last_two_points)
end

# Trigger Lagrange interpolation with at least four points
if :lagrange in algorithms and length(new_state) >= 4 do
InterpolationCli.LagrangeInterpolator.interpolate(new_state)
end

{:noreply, new_state}
end

@impl true
def handle_cast(:input_finished, state) do
IO.puts("Input processing finished.")
{:noreply, state}
end
end
81 changes: 81 additions & 0 deletions lib/lagrange_interpolator.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
defmodule InterpolationCli.LagrangeInterpolator do
@moduledoc """
Module for Lagrange interpolation. Receives multiple points and computes
intermediate values with a given sampling frequency and step.
"""

use GenServer

# API
def start_link(step) do
GenServer.start_link(__MODULE__, step, name: __MODULE__)
end

def interpolate(points) do
GenServer.cast(__MODULE__, {:interpolate, points})
end

def interpolate_sync(points) do
GenServer.call(__MODULE__, {:interpolate_sync, points})
end

# Lagrange interpolation
defp perform_lagrange_interpolation(points, step) do
points = Enum.take(points, -4)
x_min = points |> hd() |> elem(0)
x_max = points |> List.last() |> elem(0)

xs =
Stream.iterate(x_min, &(&1 + step))
|> Enum.take_while(&(&1 <= x_max + step))

ys =
Enum.map(xs, fn x ->
lagrange_value(points, x)
end)

res = Enum.zip(xs, ys)

descr = """
Lagrange (from point #{Float.round(x_min, 2)} with step #{step}, covering all input X (#{Float.round(x_max, 2)} < #{Float.round(List.last(xs), 2)})):
"""

{descr, res}
end

defp lagrange_value(points, x) do
Enum.reduce(points, 0.0, fn {x_i, y_i}, acc ->
acc + y_i * basis_polynomial(points, x, x_i)
end)
end

defp basis_polynomial(points, x, x_i) do
Enum.reduce(points, 1.0, fn {x_j, _}, prod ->
if x_i != x_j do
prod * (x - x_j) / (x_i - x_j)
else
prod
end
end)
end

# Callbacks
@impl true
def init(step) do
{:ok, step}
end

@impl true
def handle_cast({:interpolate, points}, step) do
{descr, res} = perform_lagrange_interpolation(points, step)
InterpolationCli.OutputHandler.output(descr, res)
{:noreply, step}
end

@impl true
def handle_call({:interpolate_sync, points}, _from, step) do
{descr, res} = perform_lagrange_interpolation(points, step)
InterpolationCli.OutputHandler.output_sync(descr, res)
{:reply, :ok, step}
end
end
25 changes: 13 additions & 12 deletions lib/linear_interpolator.ex
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
defmodule InterpolationCli.LinearInterpolator do
@moduledoc """
Module for linear interpolation. Receives a window of points from InputHandler and
computes intermediate values with a given sampling frequency and step, then sends results to OutputHandler.
Module for linear interpolation. Receives a window of points and computes
intermediate values with a given sampling frequency and step.
"""

use GenServer

# API
def start_link([frequency, step]) do
GenServer.start_link(__MODULE__, {frequency, step}, name: __MODULE__)
def start_link(step) do
GenServer.start_link(__MODULE__, step, name: __MODULE__)
end

def interpolate(points) do
Expand All @@ -19,6 +19,7 @@ defmodule InterpolationCli.LinearInterpolator do
GenServer.call(__MODULE__, {:interpolate_sync, points})
end

# Linear interpolation
def perform_linear_interpolation(points, step) do
[{x1, y1}, {x2, y2}] = points

Expand All @@ -35,21 +36,21 @@ defmodule InterpolationCli.LinearInterpolator do

# Callbacks
@impl true
def init({frequency, step}) do
{:ok, %{frequency: frequency, step: step}}
def init(step) do
{:ok, step}
end

@impl true
def handle_cast({:interpolate, points}, state) do
{descr, res} = perform_linear_interpolation(points, state.step)
def handle_cast({:interpolate, points}, step) do
{descr, res} = perform_linear_interpolation(points, step)
InterpolationCli.OutputHandler.output(descr, res)
{:noreply, state}
{:noreply, step}
end

@impl true
def handle_call({:interpolate_sync, points}, _from, state) do
{descr, res} = perform_linear_interpolation(points, state.step)
def handle_call({:interpolate_sync, points}, _from, step) do
{descr, res} = perform_linear_interpolation(points, step)
InterpolationCli.OutputHandler.output_sync(descr, res)
{:reply, :ok, state}
{:reply, :ok, step}
end
end
Loading

0 comments on commit 8242690

Please sign in to comment.