Repository: dayyass/calculus-of-variations (language: python)

Issue:
release v0.3.0
- fixed relative paths (#14)
- added pip install (#7)
fix relative paths
Closes #14 


Discussion / hints:

Fix patch:
diff --git a/.coveragerc b/.coveragerc
index 7cd846a..80864c0 100644
--- a/.coveragerc
+++ b/.coveragerc
@@ -1,11 +1,15 @@
 [run]
 branch = True
-source = calculus_of_variations
+source = src/calculus_of_variations
 
 [report]
 exclude_lines =
-    if self.debug:
     pragma: no cover
+    if self\.debug
+    raise AssertionError
     raise NotImplementedError
     if __name__ == .__main__.:
+    ...
+
+show_missing = True
 ignore_errors = False
diff --git a/.dockerignore b/.dockerignore
index 11c3633..79c3581 100644
--- a/.dockerignore
+++ b/.dockerignore
@@ -1,12 +1,22 @@
 **.DS_Store
-**__pycache__/
-**.cache/
-**.pyc
-**.mypy_cache/
-**.pytest_cache/
+**.python-version
+**.ipynb
+**.ipynb_checkpoints
+
+# cache
+**__pycache__
+**.mypy_cache
+**.cache
+
+# coverage
 **.coverage
 **coverage.xml
-**presentation.pdf
-/venv/
+
+venv
 .idea
+dist
 .git
+
+src/calculus_of_variations.egg-info/
+
+**presentation.pdf
diff --git a/.github/workflows/codecov.yml b/.github/workflows/codecov.yml
index ca7e851..a14f674 100644
--- a/.github/workflows/codecov.yml
+++ b/.github/workflows/codecov.yml
@@ -1,26 +1,33 @@
+# This workflow will install Python dependencies and run codecov
 # https://github.com/codecov/codecov-action#example-workflowyml-with-codecov-action
 
 name: codecov
-on: [push]
+
+on:
+  push:
+    branches: [master, develop]
+  pull_request:
+    branches: [master, develop]
+
 jobs:
-  run:
+  build:
     runs-on: ${{ matrix.os }}
     strategy:
       matrix:
-        os: [ubuntu-latest, macos-latest, windows-latest]
-    env:
-      OS: ${{ matrix.os }}
-      PYTHON: '3.8'
+        os: [ubuntu-latest]
     steps:
     - uses: actions/checkout@master
-    - name: Setup Python
+    - name: Set up Python
       uses: actions/setup-python@master
       with:
-        python-version: 3.8
-    - name: Generate coverage report
+        python-version: 3.7
+    - name: Install dependencies
       run: |
         pip install --upgrade pip
         pip install -r requirements.txt
+        pip install pytest pytest-cov
+    - name: Generate coverage report
+      run: |
         pytest --cov=./ --cov-report=xml
     - name: Upload coverage to Codecov
       uses: codecov/codecov-action@v1
@@ -28,4 +35,4 @@ jobs:
         flags: unittests
         env_vars: OS,PYTHON
         fail_ci_if_error: true
-        verbose: true
\ No newline at end of file
+        verbose: true
diff --git a/.github/workflows/linter.yml b/.github/workflows/linter.yml
index ce613a2..e752342 100644
--- a/.github/workflows/linter.yml
+++ b/.github/workflows/linter.yml
@@ -1,23 +1,26 @@
-# This workflow will install Python dependencies, run tests and lint with a single version of Python
+# This workflow will install Python dependencies and run linter
 # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
 
 name: linter
 
 on:
   push:
-    branches: [ master ]
+    branches: [master, develop]
   pull_request:
-    branches: [ master ]
+    branches: [master, develop]
 
 jobs:
   build:
-    runs-on: ubuntu-latest
+    runs-on: ${{ matrix.os }}
+    strategy:
+      matrix:
+        os: [ubuntu-latest]
     steps:
     - uses: actions/checkout@v2
-    - name: Set up Python 3.8
+    - name: Set up Python
       uses: actions/setup-python@v2
       with:
-        python-version: 3.8
+        python-version: 3.7
     - name: Install dependencies
       run: |
         pip install --upgrade pip
@@ -35,4 +38,4 @@ jobs:
         # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide
         flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics
     - name: Type check with mypy
-      run: mypy --ignore-missing-imports .
\ No newline at end of file
+      run: mypy --ignore-missing-imports .
diff --git a/.gitignore b/.gitignore
index 3f3364c..eb49555 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,6 +1,19 @@
 *.DS_Store
+.python-version
+*.ipynb
+*.ipynb_checkpoints
+
+# cache
+*__pycache__
+*.mypy_cache
+*.cache
+
+# coverage
 *.coverage
-coverage.xml
-*__pycache__/
-/venv/
-.idea
\ No newline at end of file
+*coverage.xml
+
+venv
+.idea
+dist
+
+*.egg-info/
diff --git a/Dockerfile b/Dockerfile
index b91d01b..41f01db 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -1,6 +1,7 @@
-FROM python:3.9.5-slim-buster
+FROM python:3.7-slim-buster
 MAINTAINER Dani El-Ayyass <dayyass@yandex.ru>
-WORKDIR /app
+
+WORKDIR /workdir
 COPY . .
 
 # instal dependencies
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..09ad56a
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,16 @@
+all:
+	./examples.sh
+coverage:
+	coverage run -m unittest discover && coverage report -m
+docker_build:
+	docker image build -t calculus_of_variations .
+docker_run:
+	docker container run -it -p 8050:8050 calculus_of_variations
+pypi_packages:
+	pip install --upgrade build twine
+pypi_build:
+	python -m build
+pypi_twine:
+	python -m twine upload --repository testpypi dist/*
+pypi_clean:
+	rm -rf dist src/calculus_of_variations.egg-info
diff --git a/README.md b/README.md
index 05e4933..0f357bd 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,24 @@
-[![tests](https://github.com/dayyass/calculus_of_variations/actions/workflows/tests.yml/badge.svg)](https://github.com/dayyass/calculus_of_variations/actions/workflows/tests.yml)
-[![linter](https://github.com/dayyass/calculus_of_variations/actions/workflows/linter.yml/badge.svg)](https://github.com/dayyass/calculus_of_variations/actions/workflows/linter.yml)
-[![codecov](https://codecov.io/gh/dayyass/calculus_of_variations/branch/master/graph/badge.svg?token=H8OFWPPUOY)](https://codecov.io/gh/dayyass/calculus_of_variations)
-[![license](https://img.shields.io/github/license/dayyass/calculus_of_variations)](https://github.com/dayyass/calculus_of_variations/blob/master/LICENSE)
-[![release (latest by date)](https://img.shields.io/github/v/release/dayyass/calculus_of_variations)](https://github.com/dayyass/calculus_of_variations/releases/latest)
-[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-brightgreen?logo=pre-commit&logoColor=white)](https://github.com/dayyass/calculus_of_variations/blob/master/.pre-commit-config.yaml)
+[![tests](https://github.com/dayyass/calculus-of-variations/actions/workflows/tests.yml/badge.svg)](https://github.com/dayyass/calculus-of-variations/actions/workflows/tests.yml)
+[![linter](https://github.com/dayyass/calculus-of-variations/actions/workflows/linter.yml/badge.svg)](https://github.com/dayyass/calculus-of-variations/actions/workflows/linter.yml)
+[![codecov](https://codecov.io/gh/dayyass/calculus-of-variations/branch/master/graph/badge.svg?token=H8OFWPPUOY)](https://codecov.io/gh/dayyass/calculus-of-variations)
+
+[![python 3.6](https://img.shields.io/badge/python-3.6-blue.svg)](https://github.com/dayyass/calculus-of-variations#requirements)
+[![release (latest by date)](https://img.shields.io/github/v/release/dayyass/calculus-of-variations)](https://github.com/dayyass/calculus-of-variations/releases/latest)
+[![license](https://img.shields.io/github/license/dayyass/calculus-of-variations?color=blue)](https://github.com/dayyass/calculus-of-variations/blob/main/LICENSE)
+
+[![pre-commit](https://img.shields.io/badge/pre--commit-enabled-black)](https://github.com/dayyass/calculus-of-variations/blob/main/.pre-commit-config.yaml)
 [![code style: black](https://img.shields.io/badge/code%20style-black-000000.svg)](https://github.com/psf/black)
 
+[![pypi version](https://img.shields.io/pypi/v/calculus-of-variations)](https://pypi.org/project/calculus-of-variations)
+[![pypi downloads](https://img.shields.io/pypi/dm/calculus-of-variations)](https://pypi.org/project/calculus-of-variations)
+
+
 ### About
 My bachelor project on solving the **Calculus of variations** problems using symbolic mathematics (2018).
 
-I [*participated*](https://it-mm.rea.ru/uploads/arhiv/2019/sertificat/299.pdf) with this project at the IX International Scientific and Practical Conference named after A.I. Kitov "Information Technologies and Mathematical Methods in Economics and Management" ([*link*](https://it-mm.rea.ru/eng)).<br>
+I [participated](https://it-mm.rea.ru/uploads/arhiv/2019/sertificat/299.pdf) with this project at the IX International Scientific and Practical Conference named after A.I. Kitov "Information Technologies and Mathematical Methods in Economics and Management" ([link](https://it-mm.rea.ru/eng)).<br>
 
-My [*presentation*](presentation.pdf) about this project.
+My [presentation](presentation.pdf) about this project.
 
 ### What is Calculus of variations
 According to [Wikipedia](https://en.wikipedia.org/wiki/Calculus_of_variations):<br>
@@ -50,76 +57,118 @@ Functions that maximize or minimize functionals may be found using the [*Euler
 Arbitrary constants arising when solving this differential equation, find them from the given boundary conditions.
 
 ### List of supported problems
-- [x] [*Simplest problem*](https://github.com/dayyass/calculus_of_variations/wiki/Simplest-problem)
-- [x] [*Boltz problem*](https://github.com/dayyass/calculus_of_variations/wiki/Boltz-problem)
-- [x] [*Isoperimetric problem*](https://github.com/dayyass/calculus_of_variations/wiki/Isoperimetric-problem)
-- [x] [*Higher derivatives problem*](https://github.com/dayyass/calculus_of_variations/wiki/Higher-derivatives-problem)
-- [x] [*Multidimensional problem*](https://github.com/dayyass/calculus_of_variations/wiki/Multidimensional-problem)
+- [Simplest problem](https://github.com/dayyass/calculus-of-variations/wiki/Simplest-problem)
+- [Boltz problem](https://github.com/dayyass/calculus-of-variations/wiki/Boltz-problem)
+- [Isoperimetric problem](https://github.com/dayyass/calculus-of-variations/wiki/Isoperimetric-problem)
+- [Higher derivatives problem](https://github.com/dayyass/calculus-of-variations/wiki/Higher-derivatives-problem)
+- [Multidimensional problem](https://github.com/dayyass/calculus-of-variations/wiki/Multidimensional-problem)
 
-More about each task in [*project wiki*](https://github.com/dayyass/calculus_of_variations/wiki).
+More about each task in [project wiki](https://github.com/dayyass/calculus-of-variations/wiki).
 
 ### Usage
-First, install dependencies:
+First, install the package:
 ```
-# clone repo (https/ssh)
-git clone https://github.com/dayyass/calculus_of_variations.git
-# git clone git@github.com:dayyass/calculus_of_variations.git
-
-# install dependencies (preferable in venv)
-cd calculus_of_variations
-pip install -r requirements.txt
+pip install calculus-of-variations
 ```
 
 Usage for example above:
-```
-python calculus_of_variations/simplest_problem.py -L "x_diff ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1"
-```
+```python3
+import calculus_of_variations
+
+solver = calculus_of_variations.SimplestSolver(
+    L="x_diff ** 2 + t * x",
+    t0="0", t1="1",
+    x0="0", x1="0",
+)
+
+solver.solve()
 
-General cases:
+# integral from 0 to 1 of (x_diff ** 2 + t * x)dt -> extr
+# x(0) = 0
+# x(1) = 0
+
+# general_solution: C1 + C2*t + t**3/12
+# coefficients: {C1: 0, C2: -1/12}
+# particular_solution: t**3/12 - t/12
+# extrema_value: -1/180
 ```
+
+Other cases:
+```python3
 # Simplest problem
-python calculus_of_variations/simplest_problem.py -L {str} -t0 {str} -t1 {str} -x0 {str} -x1 {str}
+solver = calculus_of_variations.SimplestSolver(
+    L="x_diff ** 2",
+    t0="0", t1="1",
+    x0="0", x1="1",
+)
+solver.solve()
 
 # Boltz problem
-python calculus_of_variations/boltz_problem.py -L {str} -l {str} -t0 {str} -t1 {str}
+solver = calculus_of_variations.BoltzSolver(
+    L="x_diff ** 2 + 2 * x",
+    l="x_t0 ** 2",
+    t0="0", t1="1",
+)
+solver.solve()
 
 # Isoperimetric problem
-python calculus_of_variations/isoperimetric_problem.py -f0 {str} -t0 {str} -t1 {str} -x0 {str} -x1 {str} -f_list "{str_1}, {str_2}, ..." -alpha_list "{str_1}, {str_2}, ..."
+solver = calculus_of_variations.IsoperimetricSolver(
+    f0="x_diff ** 2",
+    t0="0", t1="1",
+    x0="0", x1="1",
+    f_list="x",
+    alpha_list="0",
+)
+solver.solve()
 
 # Higher derivatives problem
-python calculus_of_variations/higher_derivatives_problem.py -n {str} -L {str} -t0 {str} -t1 {str} -x0 {str} -x1 {str} -x0_array "{str_1}, {str_2}, ..." -x1_array "{str_1}, {str_2}, ..."
+solver = calculus_of_variations.HigherDerivativesSolver(
+    n="2",
+    L="x_diff_2 ** 2",
+    t0="0", t1="1",
+    x0="0", x1="0",
+    x0_array="0",
+    x1_array="1",
+)
+solver.solve()
 
 # Multidimensional problem
-python calculus_of_variations/multidimensional_problem.py -L {str} -t0 {str} -t1 {str} -x1_0 {str} -x1_1 {str} -x2_0 {str} -x2_1 {str}
+solver = calculus_of_variations.MultidimensionalSolver(
+    L="x1_diff**2 + x2_diff**2",
+    t0="0", t1="1",
+    x1_0="0", x1_1="1",
+    x2_0="0", x2_1="1",
+)
+solver.solve()
 ```
 
-For specific examples see [**examples.sh**](https://github.com/dayyass/calculus_of_variations/blob/master/examples.sh).<br>
-List of **allowed functions** that you can use as parameters: [*link*](https://github.com/dayyass/calculus_of_variations/wiki/Allowed-functions).
+For specific examples see [**examples.sh**](https://github.com/dayyass/calculus-of-variations/blob/master/examples.sh).<br>
+List of **allowed functions** that you can use as parameters: [*link*](https://github.com/dayyass/calculus-of-variations/wiki/Allowed-functions).
 
 ### Web-interface
 The project supports simple web-interface for solving problems.
 You can specify **host** (`--host`) and **port** (`--port`) (default values: `host: 127.0.0.1` and `port: 8050`):
 ```
 # Simplest problem
-python web_interface/simplest_problem_dash.py --host "127.0.0.1" --port 8050
+python -m web_interface.simplest_problem_dash --host "127.0.0.1" --port 8050
 
 # Boltz problem
-python web_interface/boltz_problem_dash.py --host "127.0.0.1" --port 8050
+python -m web_interface.boltz_problem_dash --host "127.0.0.1" --port 8050
 
 # Isoperimetric problem
-python web_interface/isoperimetric_problem_dash.py --host "127.0.0.1" --port 8050
+python -m web_interface.isoperimetric_problem_dash --host "127.0.0.1" --port 8050
 
 # Higher derivatives problem
-python web_interface/higher_derivatives_problem_dash.py --host "127.0.0.1" --port 8050
+python -m web_interface.higher_derivatives_problem_dash --host "127.0.0.1" --port 8050
 
 # Multidimensional problem
-python web_interface/multidimensional_problem_dash.py --host "127.0.0.1" --port 8050
+python -m web_interface.multidimensional_problem_dash --host "127.0.0.1" --port 8050
 ```
 
 You can also launch web-interface using docker.<br>
 To build docker image run:
 ```
-docker build -t calculus_of_variations .
+docker image build -t calculus_of_variations .
 ```
 You can also pull image from [Docker Hub](https://hub.docker.com/r/dayyass/calculus_of_variations):
 ```
@@ -128,27 +177,30 @@ docker pull dayyass/calculus_of_variations
 
 To start docker container run (example for **simplest_problem_dash**):
 ```
-docker run -d -p 8050:8050 --name calculus_of_variations calculus_of_variations python web_interface/simplest_problem_dash.py --host 0.0.0.0 --port 8050
+docker container run -d -p 8050:8050 --name calculus_of_variations calculus_of_variations python -m web_interface.simplest_problem_dash --host 0.0.0.0 --port 8050
 ```
 To access web-interface go to `http://localhost:8050`
 
 ### Tests
-To launch [**tests**](https://github.com/dayyass/calculus_of_variations/tree/master/tests) run one of the following commands:<br>
-`pytest` or `python -m unittest discover`
+To launch [**tests**](https://github.com/dayyass/calculus-of-variations/tree/master/tests) run the following commands:<br>
+`python -m unittest discover`
 
 To use [**pre-commit**](https://pre-commit.com) hooks run:<br>
 `pre-commit install`
 
-To measure [**code coverage**](https://coverage.readthedocs.io) run one of the following commands:<br>
-`coverage run -m pytest && coverage report -m` or `coverage run -m unittest discover && coverage report -m`
+To measure [**code coverage**](https://coverage.readthedocs.io) run the following commands:<br>
+`coverage run -m unittest discover && coverage report -m`
+
+## Requirements
+Python >= 3.6
 
 ### Citation
-If you use **calculus_of_variations** in a scientific publication, we would appreciate references to the following BibTex entry:
+If you use **calculus-of-variations** in a scientific publication, we would appreciate references to the following BibTex entry:
 ```bibtex
 @misc{dayyass2018variations,
     author       = {El-Ayyass, Dani},
     title        = {Calculus of Variations problems solving using symbolic mathematics},
-    howpublished = {\url{https://github.com/dayyass/calculus_of_variations}},
+    howpublished = {\url{https://github.com/dayyass/calculus-of-variations}},
     year         = {2018}
 }
 ```
diff --git a/examples.sh b/examples.sh
old mode 100644
new mode 100755
index a8b431b..cd78d99
--- a/examples.sh
+++ b/examples.sh
@@ -1,32 +1,32 @@
 # simplest problem
-python calculus_of_variations/simplest_problem.py -L "x_diff ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1"
-python calculus_of_variations/simplest_problem.py -L "x_diff ** 2 + t * x" -t0 "0" -t1 "1" -x0 "0" -x1 "0"
-python calculus_of_variations/simplest_problem.py -L "t * x_diff ** 2" -t0 "1" -t1 "E" -x0 "0" -x1 "1"
-python calculus_of_variations/simplest_problem.py -L "x_diff ** 2 + x_diff * x + 12 * t * x" -t0 "0" -t1 "1" -x0 "0" -x1 "0"
-python calculus_of_variations/simplest_problem.py -L "x_diff ** 2 - t ** 2 * x" -t0 "0" -t1 "1" -x0 "0" -x1 "0"
+python -m src.calculus_of_variations.simplest_problem -L "x_diff ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1"
+python -m src.calculus_of_variations.simplest_problem -L "x_diff ** 2 + t * x" -t0 "0" -t1 "1" -x0 "0" -x1 "0"
+python -m src.calculus_of_variations.simplest_problem -L "t * x_diff ** 2" -t0 "1" -t1 "E" -x0 "0" -x1 "1"
+python -m src.calculus_of_variations.simplest_problem -L "x_diff ** 2 + x_diff * x + 12 * t * x" -t0 "0" -t1 "1" -x0 "0" -x1 "0"
+python -m src.calculus_of_variations.simplest_problem -L "x_diff ** 2 - t ** 2 * x" -t0 "0" -t1 "1" -x0 "0" -x1 "0"
 
 # boltz problem
-python calculus_of_variations/boltz_problem.py -L "x_diff ** 2 + 2 * x" -l "x_t0 ** 2" -t0 "0" -t1 "1"
-python calculus_of_variations/boltz_problem.py -L "x_diff ** 2 - x" -l "-x_t1 ** 2 / 2" -t0 "0" -t1 "1"
-python calculus_of_variations/boltz_problem.py -L "t ** 2 * x_diff ** 2" -l "-2 * x_t0 + x_t1 ** 2" -t0 "1" -t1 "2"
-python calculus_of_variations/boltz_problem.py -L "2 * (t * x_diff ** 2 + x_diff * x)" -l "3 * x_t0 ** 2 - x_t1 ** 2 - 4 * x_t1" -t0 "1" -t1 "E"
+python -m src.calculus_of_variations.boltz_problem -L "x_diff ** 2 + 2 * x" -l "x_t0 ** 2" -t0 "0" -t1 "1"
+python -m src.calculus_of_variations.boltz_problem -L "x_diff ** 2 - x" -l "-x_t1 ** 2 / 2" -t0 "0" -t1 "1"
+python -m src.calculus_of_variations.boltz_problem -L "t ** 2 * x_diff ** 2" -l "-2 * x_t0 + x_t1 ** 2" -t0 "1" -t1 "2"
+python -m src.calculus_of_variations.boltz_problem -L "2 * (t * x_diff ** 2 + x_diff * x)" -l "3 * x_t0 ** 2 - x_t1 ** 2 - 4 * x_t1" -t0 "1" -t1 "E"
 
 # isoperimetric problem
-python calculus_of_variations/isoperimetric_problem.py -f0 "x_diff ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1" -f_list "x" -alpha_list "0"
-python calculus_of_variations/isoperimetric_problem.py -f0 "x_diff ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1" -f_list "t * x" -alpha_list "0"
-python calculus_of_variations/isoperimetric_problem.py -f0 "x_diff ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "0" -f_list "x, t * x" -alpha_list "1, 0"
-python calculus_of_variations/isoperimetric_problem.py -f0 "x_diff ** 2" -t0 "0" -t1 "pi" -x0 "1" -x1 "-1" -f_list "x * cos(t)" -alpha_list "pi / 2"
-python calculus_of_variations/isoperimetric_problem.py -f0 "t ** 2 * x_diff ** 2" -t0 "1" -t1 "2" -x0 "1" -x1 "2" -f_list "t * x" -alpha_list "7 / 3"
+python -m src.calculus_of_variations.isoperimetric_problem -f0 "x_diff ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1" -f_list "x" -alpha_list "0"
+python -m src.calculus_of_variations.isoperimetric_problem -f0 "x_diff ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1" -f_list "t * x" -alpha_list "0"
+python -m src.calculus_of_variations.isoperimetric_problem -f0 "x_diff ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "0" -f_list "x, t * x" -alpha_list "1, 0"
+python -m src.calculus_of_variations.isoperimetric_problem -f0 "x_diff ** 2" -t0 "0" -t1 "pi" -x0 "1" -x1 "-1" -f_list "x * cos(t)" -alpha_list "pi / 2"
+python -m src.calculus_of_variations.isoperimetric_problem -f0 "t ** 2 * x_diff ** 2" -t0 "1" -t1 "2" -x0 "1" -x1 "2" -f_list "t * x" -alpha_list "7 / 3"
 
 # higher derivatives problem
-python calculus_of_variations/higher_derivatives_problem.py -n "2" -L "x_diff_2 ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "0" -x0_array "0" -x1_array "1"
-python calculus_of_variations/higher_derivatives_problem.py -n "2" -L "x_diff_2 ** 2 - 48 * x" -t0 "0" -t1 "1" -x0 "1" -x1 "0" -x0_array "-4" -x1_array "0"
-python calculus_of_variations/higher_derivatives_problem.py -n "3" -L "x_diff_3 ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1" -x0_array "0, 0" -x1_array "3, 6"
-python calculus_of_variations/higher_derivatives_problem.py -n "4" -L "x_diff_4 ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1" -x0_array "0, 0, 0" -x1_array "3, 6, 10"
+python -m src.calculus_of_variations.higher_derivatives_problem -n "2" -L "x_diff_2 ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "0" -x0_array "0" -x1_array "1"
+python -m src.calculus_of_variations.higher_derivatives_problem -n "2" -L "x_diff_2 ** 2 - 48 * x" -t0 "0" -t1 "1" -x0 "1" -x1 "0" -x0_array "-4" -x1_array "0"
+python -m src.calculus_of_variations.higher_derivatives_problem -n "3" -L "x_diff_3 ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1" -x0_array "0, 0" -x1_array "3, 6"
+python -m src.calculus_of_variations.higher_derivatives_problem -n "4" -L "x_diff_4 ** 2" -t0 "0" -t1 "1" -x0 "0" -x1 "1" -x0_array "0, 0, 0" -x1_array "3, 6, 10"
 
 # multidimensional problem
-python calculus_of_variations/multidimensional_problem.py -L "x1_diff**2 + x2_diff**2" -t0 "0" -t1 "1" -x1_0 "0" -x1_1 "1" -x2_0 "0" -x2_1 "1"
-python calculus_of_variations/multidimensional_problem.py -L "x1_diff ** 2 + x2_diff ** 2" -t0 "0" -t1 "1" -x1_0 "0" -x1_1 "1" -x2_0 "1" -x2_1 "E"
-python calculus_of_variations/multidimensional_problem.py -L "x2 ** 2 + x1_diff ** 2 + x2_diff ** 2" -t0 "0" -t1 "1" -x1_0 "0" -x1_1 "1" -x2_0 "1" -x2_1 "E"
-python calculus_of_variations/multidimensional_problem.py -L "x1_diff * x2_diff - x1 * x2" -t0 "0" -t1 "pi / 2" -x1_0 "0" -x1_1 "1" -x2_0 "1" -x2_1 "0"
-python calculus_of_variations/multidimensional_problem.py -L "2 * x1 + x2 ** 2 + x1_diff ** 2 + x2_diff ** 2" -t0 "0" -t1 "1" -x1_0 "0" -x1_1 "0.5" -x2_0 "1" -x2_1 "exp(-1)"
+python -m src.calculus_of_variations.multidimensional_problem -L "x1_diff**2 + x2_diff**2" -t0 "0" -t1 "1" -x1_0 "0" -x1_1 "1" -x2_0 "0" -x2_1 "1"
+python -m src.calculus_of_variations.multidimensional_problem -L "x1_diff ** 2 + x2_diff ** 2" -t0 "0" -t1 "1" -x1_0 "0" -x1_1 "1" -x2_0 "1" -x2_1 "E"
+python -m src.calculus_of_variations.multidimensional_problem -L "x2 ** 2 + x1_diff ** 2 + x2_diff ** 2" -t0 "0" -t1 "1" -x1_0 "0" -x1_1 "1" -x2_0 "1" -x2_1 "E"
+python -m src.calculus_of_variations.multidimensional_problem -L "x1_diff * x2_diff - x1 * x2" -t0 "0" -t1 "pi / 2" -x1_0 "0" -x1_1 "1" -x2_0 "1" -x2_1 "0"
+python -m src.calculus_of_variations.multidimensional_problem -L "2 * x1 + x2 ** 2 + x1_diff ** 2 + x2_diff ** 2" -t0 "0" -t1 "1" -x1_0 "0" -x1_1 "0.5" -x2_0 "1" -x2_1 "exp(-1)"
diff --git a/pyproject.toml b/pyproject.toml
new file mode 100644
index 0000000..9787c3b
--- /dev/null
+++ b/pyproject.toml
@@ -0,0 +1,3 @@
+[build-system]
+requires = ["setuptools", "wheel"]
+build-backend = "setuptools.build_meta"
diff --git a/requirements.txt b/requirements.txt
index 632ec7d..cf6236d 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -1,39 +1,5 @@
-appdirs==1.4.4
-attrs==21.2.0
-Brotli==1.0.9
-cfgv==3.2.0
-click==7.1.2
-coverage==5.5
+coverage==5.5  # dev
 dash==1.17.0
-dash-core-components==1.13.0
-dash-html-components==1.1.1
-dash-renderer==1.8.3
-dash-table==4.11.0
-distlib==0.3.1
-filelock==3.0.12
-Flask==1.1.2
-Flask-Compress==1.8.0
-future==0.18.2
-identify==1.5.10
-iniconfig==1.1.1
-itsdangerous==1.1.0
-Jinja2==2.11.3
-MarkupSafe==1.1.1
-mpmath==1.1.0
-nodeenv==1.5.0
-packaging==20.9
-parameterized==0.7.4
-plotly==4.12.0
-pluggy==0.13.1
-pre-commit==2.9.3
-py==1.10.0
-pyparsing==2.4.7
-pytest==6.2.4
-pytest-cov==2.12.1
-PyYAML==5.4
-retrying==1.3.3
-six==1.15.0
+parameterized==0.7.4  # dev
+pre-commit==2.9.3  # dev
 sympy==1.6.2
-toml==0.10.2
-virtualenv==20.2.2
-Werkzeug==1.0.1
diff --git a/setup.cfg b/setup.cfg
new file mode 100644
index 0000000..4eef762
--- /dev/null
+++ b/setup.cfg
@@ -0,0 +1,26 @@
+[metadata]
+name = calculus-of-variations
+version = 0.3.0
+author = Dani El-Ayyass
+author_email = dayyass@yandex.ru
+description = Project on solving the Calculus of variations problems using symbolic mathematics
+long_description = file: README.md
+long_description_content_type = text/markdown
+url = https://github.com/dayyass/calculus-of-variations
+project_urls =
+    Bug Tracker = https://github.com/dayyass/calculus-of-variations/issues
+classifiers =
+    Programming Language :: Python :: 3
+    License :: OSI Approved :: MIT License
+    Operating System :: OS Independent
+
+[options]
+package_dir =
+    = src
+packages = find:
+python_requires = >=3.6
+install_requires =
+    sympy == 1.6.2
+
+[options.packages.find]
+where = src
diff --git a/calculus_of_variations/__init__.py b/src/calculus_of_variations/__init__.py
similarity index 94%
rename from calculus_of_variations/__init__.py
rename to src/calculus_of_variations/__init__.py
index ed2fb55..865b483 100644
--- a/calculus_of_variations/__init__.py
+++ b/src/calculus_of_variations/__init__.py
@@ -4,6 +4,8 @@
 from .multidimensional_problem import MultidimensionalSolver
 from .simplest_problem import SimplestSolver
 
+__version__ = "0.3.0"
+
 __all__ = [
     "SimplestSolver",
     "BoltzSolver",
diff --git a/calculus_of_variations/abstract_problem.py b/src/calculus_of_variations/abstract_problem.py
similarity index 100%
rename from calculus_of_variations/abstract_problem.py
rename to src/calculus_of_variations/abstract_problem.py
diff --git a/calculus_of_variations/boltz_problem.py b/src/calculus_of_variations/boltz_problem.py
similarity index 94%
rename from calculus_of_variations/boltz_problem.py
rename to src/calculus_of_variations/boltz_problem.py
index 91757a7..8ece556 100644
--- a/calculus_of_variations/boltz_problem.py
+++ b/src/calculus_of_variations/boltz_problem.py
@@ -1,12 +1,9 @@
-import sys
 from argparse import ArgumentParser
 
 from sympy import diff, dsolve, integrate, solve, var
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations.abstract_problem import AbstractSolver
-from calculus_of_variations.utils import sympy_eval, t, x, x_diff, x_t0, x_t1
+from .abstract_problem import AbstractSolver
+from .utils import sympy_eval, t, x, x_diff, x_t0, x_t1
 
 
 class BoltzSolver(AbstractSolver):
diff --git a/calculus_of_variations/higher_derivatives_problem.py b/src/calculus_of_variations/higher_derivatives_problem.py
similarity index 97%
rename from calculus_of_variations/higher_derivatives_problem.py
rename to src/calculus_of_variations/higher_derivatives_problem.py
index 219508b..ee54293 100644
--- a/calculus_of_variations/higher_derivatives_problem.py
+++ b/src/calculus_of_variations/higher_derivatives_problem.py
@@ -1,12 +1,9 @@
-import sys
 from argparse import ArgumentParser
 
 from sympy import diff, dsolve, integrate, solve, var
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations.abstract_problem import AbstractSolver
-from calculus_of_variations.utils import (  # noqa: F401
+from .abstract_problem import AbstractSolver
+from .utils import (  # noqa: F401
     sympy_eval,
     t,
     x,
diff --git a/calculus_of_variations/isoperimetric_problem.py b/src/calculus_of_variations/isoperimetric_problem.py
similarity index 96%
rename from calculus_of_variations/isoperimetric_problem.py
rename to src/calculus_of_variations/isoperimetric_problem.py
index e8f2beb..f12d7db 100644
--- a/calculus_of_variations/isoperimetric_problem.py
+++ b/src/calculus_of_variations/isoperimetric_problem.py
@@ -1,12 +1,9 @@
-import sys
 from argparse import ArgumentParser
 
 from sympy import diff, dsolve, integrate, solve, var
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations.abstract_problem import AbstractSolver
-from calculus_of_variations.utils import sympy_eval, t, x, x_diff
+from .abstract_problem import AbstractSolver
+from .utils import sympy_eval, t, x, x_diff
 
 
 class IsoperimetricSolver(AbstractSolver):
diff --git a/calculus_of_variations/multidimensional_problem.py b/src/calculus_of_variations/multidimensional_problem.py
similarity index 97%
rename from calculus_of_variations/multidimensional_problem.py
rename to src/calculus_of_variations/multidimensional_problem.py
index 177368e..eb2b2fc 100644
--- a/calculus_of_variations/multidimensional_problem.py
+++ b/src/calculus_of_variations/multidimensional_problem.py
@@ -1,12 +1,9 @@
-import sys
 from argparse import ArgumentParser
 
 from sympy import diff, dsolve, integrate, solve, var
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations.abstract_problem import AbstractSolver
-from calculus_of_variations.utils import sympy_eval, t, x1, x1_diff, x2, x2_diff
+from .abstract_problem import AbstractSolver
+from .utils import sympy_eval, t, x1, x1_diff, x2, x2_diff
 
 
 class MultidimensionalSolver(AbstractSolver):
diff --git a/calculus_of_variations/simplest_problem.py b/src/calculus_of_variations/simplest_problem.py
similarity index 94%
rename from calculus_of_variations/simplest_problem.py
rename to src/calculus_of_variations/simplest_problem.py
index f973dc1..4f88b87 100644
--- a/calculus_of_variations/simplest_problem.py
+++ b/src/calculus_of_variations/simplest_problem.py
@@ -1,12 +1,9 @@
-import sys
 from argparse import ArgumentParser
 
 from sympy import diff, dsolve, integrate, solve, var
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations.abstract_problem import AbstractSolver
-from calculus_of_variations.utils import sympy_eval, t, x, x_diff
+from .abstract_problem import AbstractSolver
+from .utils import sympy_eval, t, x, x_diff
 
 
 class SimplestSolver(AbstractSolver):
diff --git a/calculus_of_variations/utils.py b/src/calculus_of_variations/utils.py
similarity index 99%
rename from calculus_of_variations/utils.py
rename to src/calculus_of_variations/utils.py
index ff60de6..a854007 100644
--- a/calculus_of_variations/utils.py
+++ b/src/calculus_of_variations/utils.py
@@ -86,7 +86,6 @@
 x2_diff = diff(x2, t)
 
 
-# TODO: ast eval
 def sympy_eval(string: str) -> Any:
     """
     Eval string.
diff --git a/web_interface/__init__.py b/web_interface/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/web_interface/boltz_problem_dash.py b/web_interface/boltz_problem_dash.py
index 64b6825..7dd7007 100644
--- a/web_interface/boltz_problem_dash.py
+++ b/web_interface/boltz_problem_dash.py
@@ -1,14 +1,11 @@
-import sys
-
 import dash
 import dash_core_components as dcc
 import dash_html_components as html
 from dash.dependencies import Input, Output, State
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import BoltzSolver
-from web_interface.utils import dash_answer, dash_boltz_problem, get_argparse
+from src.calculus_of_variations import BoltzSolver
+
+from .utils import dash_answer, dash_boltz_problem, get_argparse
 
 external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
 app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
diff --git a/web_interface/higher_derivatives_problem_dash.py b/web_interface/higher_derivatives_problem_dash.py
index 2efdb0f..a65a85f 100644
--- a/web_interface/higher_derivatives_problem_dash.py
+++ b/web_interface/higher_derivatives_problem_dash.py
@@ -1,18 +1,11 @@
-import sys
-
 import dash
 import dash_core_components as dcc
 import dash_html_components as html
 from dash.dependencies import Input, Output, State
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import HigherDerivativesSolver
-from web_interface.utils import (
-    dash_answer,
-    dash_higher_derivatives_problem,
-    get_argparse,
-)
+from src.calculus_of_variations import HigherDerivativesSolver
+
+from .utils import dash_answer, dash_higher_derivatives_problem, get_argparse
 
 external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
 app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
diff --git a/web_interface/isoperimetric_problem_dash.py b/web_interface/isoperimetric_problem_dash.py
index dcc90a3..f4fa97f 100644
--- a/web_interface/isoperimetric_problem_dash.py
+++ b/web_interface/isoperimetric_problem_dash.py
@@ -1,14 +1,11 @@
-import sys
-
 import dash
 import dash_core_components as dcc
 import dash_html_components as html
 from dash.dependencies import Input, Output, State
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import IsoperimetricSolver
-from web_interface.utils import dash_answer, dash_isoperimetric_problem, get_argparse
+from src.calculus_of_variations import IsoperimetricSolver
+
+from .utils import dash_answer, dash_isoperimetric_problem, get_argparse
 
 external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
 app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
diff --git a/web_interface/multidimensional_problem_dash.py b/web_interface/multidimensional_problem_dash.py
index 7ebf539..d5429ef 100644
--- a/web_interface/multidimensional_problem_dash.py
+++ b/web_interface/multidimensional_problem_dash.py
@@ -1,14 +1,11 @@
-import sys
-
 import dash
 import dash_core_components as dcc
 import dash_html_components as html
 from dash.dependencies import Input, Output, State
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import MultidimensionalSolver
-from web_interface.utils import (
+from src.calculus_of_variations import MultidimensionalSolver
+
+from .utils import (
     dash_multidimensional_answer,
     dash_multidimensional_problem,
     get_argparse,
diff --git a/web_interface/simplest_problem_dash.py b/web_interface/simplest_problem_dash.py
index 7eeeb2b..5320d04 100644
--- a/web_interface/simplest_problem_dash.py
+++ b/web_interface/simplest_problem_dash.py
@@ -1,14 +1,11 @@
-import sys
-
 import dash
 import dash_core_components as dcc
 import dash_html_components as html
 from dash.dependencies import Input, Output, State
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import SimplestSolver
-from web_interface.utils import dash_answer, dash_simplest_problem, get_argparse
+from src.calculus_of_variations import SimplestSolver
+
+from .utils import dash_answer, dash_simplest_problem, get_argparse
 
 external_stylesheets = ["https://codepen.io/chriddyp/pen/bWLwgP.css"]
 app = dash.Dash(__name__, external_stylesheets=external_stylesheets)
diff --git a/web_interface/utils.py b/web_interface/utils.py
index fd6dda8..ce002f3 100644
--- a/web_interface/utils.py
+++ b/web_interface/utils.py
@@ -1,4 +1,3 @@
-import sys
 from argparse import ArgumentParser
 from typing import Dict, Union
 
@@ -6,9 +5,7 @@
 import dash_html_components as html
 from sympy.core.symbol import Symbol
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import (
+from src.calculus_of_variations import (
     BoltzSolver,
     HigherDerivativesSolver,
     IsoperimetricSolver,


Test patch:
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
index 4af979c..af87d1e 100644
--- a/.github/workflows/tests.yml
+++ b/.github/workflows/tests.yml
@@ -1,13 +1,13 @@
-# This workflow will install Python dependencies, run tests and lint with a variety of Python versions
+# This workflow will install Python dependencies and run tests with a variety of Python versions
 # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
 
 name: tests
 
 on:
   push:
-    branches: [ master ]
+    branches: [master, develop]
   pull_request:
-    branches: [ master ]
+    branches: [master, develop]
 
 jobs:
   build:
@@ -18,7 +18,7 @@ jobs:
         os: [ubuntu-latest, macOS-latest, windows-latest]
     steps:
     - uses: actions/checkout@v2
-    - name: Set up Python ${{ matrix.python-version }}
+    - name: Set up Python
       uses: actions/setup-python@v2
       with:
         python-version: ${{ matrix.python-version }}
@@ -26,6 +26,6 @@ jobs:
       run: |
         pip install --upgrade pip
         pip install -r requirements.txt
-    - name: Test with pytest
+    - name: Tests
       run: |
-        pytest
\ No newline at end of file
+        python -m unittest discover
diff --git a/tests/__init__.py b/tests/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/tests/test_boltz_problem.py b/tests/test_boltz_problem.py
index ebd7ffa..33740fb 100644
--- a/tests/test_boltz_problem.py
+++ b/tests/test_boltz_problem.py
@@ -1,12 +1,9 @@
-import sys
 import unittest
 
 from parameterized import parameterized_class
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import BoltzSolver
-from calculus_of_variations.utils import log, t
+from src.calculus_of_variations import BoltzSolver
+from src.calculus_of_variations.utils import log, t
 
 C1 = BoltzSolver.C1
 C2 = BoltzSolver.C2
diff --git a/tests/test_higher_derivatives_problem.py b/tests/test_higher_derivatives_problem.py
index cf232c4..22db6b1 100644
--- a/tests/test_higher_derivatives_problem.py
+++ b/tests/test_higher_derivatives_problem.py
@@ -1,12 +1,9 @@
-import sys
 import unittest
 
 from parameterized import parameterized_class
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import HigherDerivativesSolver
-from calculus_of_variations.utils import t, var
+from src.calculus_of_variations import HigherDerivativesSolver
+from src.calculus_of_variations.utils import t, var
 
 Cs = {}
 for i in range(1, 9):
diff --git a/tests/test_isoperimetric_problem.py b/tests/test_isoperimetric_problem.py
index fa2fbfe..6c2990e 100644
--- a/tests/test_isoperimetric_problem.py
+++ b/tests/test_isoperimetric_problem.py
@@ -1,12 +1,9 @@
-import sys
 import unittest
 
 from parameterized import parameterized_class
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import IsoperimetricSolver
-from calculus_of_variations.utils import cos, pi, t, var
+from src.calculus_of_variations import IsoperimetricSolver
+from src.calculus_of_variations.utils import cos, pi, t, var
 
 C1 = IsoperimetricSolver.C1
 C2 = IsoperimetricSolver.C2
diff --git a/tests/test_multidimensional_problem.py b/tests/test_multidimensional_problem.py
index 4939ca6..2d186bc 100644
--- a/tests/test_multidimensional_problem.py
+++ b/tests/test_multidimensional_problem.py
@@ -1,12 +1,9 @@
-import sys
 import unittest
 
 from parameterized import parameterized_class
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import MultidimensionalSolver
-from calculus_of_variations.utils import E, I, exp, t
+from src.calculus_of_variations import MultidimensionalSolver
+from src.calculus_of_variations.utils import E, I, exp, t
 
 C1 = MultidimensionalSolver.C1
 C2 = MultidimensionalSolver.C2
diff --git a/tests/test_simplest_problem.py b/tests/test_simplest_problem.py
index 6917ac8..2494d2a 100644
--- a/tests/test_simplest_problem.py
+++ b/tests/test_simplest_problem.py
@@ -1,12 +1,9 @@
-import sys
 import unittest
 
 from parameterized import parameterized_class
 
-# TODO: fix it
-sys.path.append("./")
-from calculus_of_variations import SimplestSolver
-from calculus_of_variations.utils import log, t
+from src.calculus_of_variations import SimplestSolver
+from src.calculus_of_variations.utils import log, t
 
 C1 = SimplestSolver.C1
 C2 = SimplestSolver.C2