Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Better testing, and more objective functions #79

Merged
merged 106 commits into from
Nov 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
106 commits
Select commit Hold shift + click to select a range
9fc4cea
Merge pull request #25 from MachineLearningLifeScience/master
miguelgondu Sep 4, 2023
def6565
Updates the environment for lambo, forces registration, adds example
miguelgondu Sep 4, 2023
6472be8
For now, we add dev installation instead of master
miguelgondu Sep 4, 2023
bfc0650
Merge pull request #27 from MachineLearningLifeScience/bugfix-cleans-…
RMichae1 Sep 4, 2023
085afe3
enabling support for unaligned problems
SimonBartels Sep 4, 2023
6ad8d5f
Merge branch 'dev' of github.com:MachineLearningLifeScience/poli into…
SimonBartels Sep 4, 2023
635102e
adds a description of how to contribute
miguelgondu Sep 4, 2023
d0359d9
Merge branch 'dev' of github.com:MachineLearningLifeScience/poli into…
miguelgondu Sep 4, 2023
e04e751
Adds three lambo objectives, including an example
miguelgondu Sep 5, 2023
6ac1f7c
Removes the lambo tests, they take too long on GitHub
miguelgondu Sep 5, 2023
5bb19ec
Merge pull request #28 from MachineLearningLifeScience/feature-adds-l…
miguelgondu Sep 5, 2023
0f9310f
Updates the readme and example for the ddr3 docking task
miguelgondu Sep 6, 2023
8628499
renames the docking task, fixing a small typo
miguelgondu Sep 6, 2023
2fffe6b
Adds SA from TDC as an objective, refactors TDC-related black boxes
miguelgondu Sep 6, 2023
a803b1e
Forgot about formatting the files
miguelgondu Sep 6, 2023
c8f607b
Merge pull request #29 from MachineLearningLifeScience/feature-add-sa…
miguelgondu Sep 6, 2023
7ccdbea
Fixes a bug on generating molecules from selfies
miguelgondu Sep 6, 2023
bdbb97c
fixed broken observer support
SimonBartels Sep 6, 2023
83c3350
Cleans up a repeated operation in tests. Moves a poli-baselines test …
miguelgondu Sep 6, 2023
83ea6a9
added problem sequence individual fix replacement in wrapper
Sep 11, 2023
343bd9c
black formatting for RFPWrapper
Sep 11, 2023
ea39a16
Merge pull request #31 from MachineLearningLifeScience/30-lambo-basic…
RMichae1 Sep 11, 2023
c458cf8
Implements repairing using the foldx interface, allows users to pass …
miguelgondu Sep 11, 2023
2f13ea8
Merge pull request #32 from MachineLearningLifeScience/feat-improving…
miguelgondu Sep 11, 2023
fa76162
Starts implementing an alternate version of foldx to include more tha…
miguelgondu Sep 12, 2023
b9ca64f
Fixes a bug on the process wrapper information passing
miguelgondu Sep 12, 2023
185c10e
adds a common black box function for foldx tasks
miguelgondu Sep 12, 2023
880437a
Removes unused packages
miguelgondu Sep 13, 2023
87c4ec3
Merge pull request #33 from MachineLearningLifeScience/feature-more-t…
miguelgondu Sep 13, 2023
d630e48
Adds parallelization to the call of the abstract black box
miguelgondu Sep 13, 2023
39a6c7d
Fixes bugs on logp, and information passing to the process wrapper wh…
miguelgondu Sep 13, 2023
d909428
Adds the right kwargs to foldx rfp lambo
miguelgondu Sep 13, 2023
a74b6aa
Runs linting on the rfp register
miguelgondu Sep 13, 2023
05783ae
Merge pull request #34 from MachineLearningLifeScience/feat-paralleli…
miguelgondu Sep 13, 2023
e1a1ea2
Adds a time comparison in the parallel query example, removes debug p…
miguelgondu Sep 13, 2023
2dd4ccc
35 refactor lambo objective and wrapper (#36)
RMichae1 Sep 15, 2023
3ffc094
Adds a try-except to the termination of external black box
miguelgondu Sep 15, 2023
0222711
Merge pull request #38 from MachineLearningLifeScience/feat-graceful-…
miguelgondu Sep 15, 2023
eb1c45a
Adds support for seeding in all objective functions, sets default see…
miguelgondu Sep 18, 2023
0d01587
set default seed None (#52)
RMichae1 Sep 18, 2023
2a91cc9
50 sees setting for foldx rfp lambo problem (#53)
RMichae1 Sep 18, 2023
107b254
Merge branch 'dev' into bugfix-add-seed-support
miguelgondu Sep 19, 2023
adb18fd
Merge pull request #51 from MachineLearningLifeScience/bugfix-add-see…
miguelgondu Sep 19, 2023
7fb9f69
update caller info to include number of starting samples (#54)
RMichae1 Sep 19, 2023
e1c21fc
pass parallelize and num_workers through, add to wrapper (#58)
RMichae1 Sep 20, 2023
7685fc2
60 add batch size to caller information lambo reference and other exp…
RMichae1 Sep 22, 2023
5b86dfc
Starts implementing an example of how to add an mlflow observer
miguelgondu Sep 26, 2023
5051529
Lints a file
miguelgondu Sep 26, 2023
7f3d659
62 reconcile poli task refactor remove all lambo code from poli repos…
RMichae1 Sep 26, 2023
ddd8ee0
Adds an example of how to make an observer using wandb
miguelgondu Sep 26, 2023
960d1f2
Merge branch 'dev' into feat-mlflow-observer-example
miguelgondu Sep 26, 2023
0336cb4
Adds registration-by-name for observers, adds an example
miguelgondu Sep 27, 2023
422a1bf
Adds a dict method to the problem setup information
miguelgondu Sep 27, 2023
09bd10a
Adds tests for observers
miguelgondu Sep 28, 2023
3b0b39e
Merge pull request #63 from MachineLearningLifeScience/feat-mlflow-ob…
miguelgondu Sep 28, 2023
eafc6c4
Establish error communication between the external observer and the i…
miguelgondu Sep 29, 2023
44a4a89
lints
miguelgondu Sep 29, 2023
f72ee59
Merge pull request #65 from MachineLearningLifeScience/bugfix-isolate…
miguelgondu Sep 29, 2023
5ad9f4c
Adds more try-excepts and information passing between observer processes
miguelgondu Oct 2, 2023
106018f
66 batched bb observations need to be passed iteratively to observer …
RMichae1 Oct 2, 2023
def98e3
call observer finish on black_box termination (#69)
RMichae1 Oct 2, 2023
7ee3fd7
71 abstract black box batched observe fails if batch shape wrong (#72)
RMichae1 Oct 3, 2023
57862c6
73 add in domain gfp task (#74)
RMichae1 Oct 3, 2023
c670cc2
Update readme to reflect new promises
miguelgondu Oct 10, 2023
b0f52d9
Make sa_tdc a module
miguelgondu Oct 10, 2023
7fa8768
Remove poli__tdc tests, because they are costly to build
miguelgondu Oct 10, 2023
3badebf
Adss missing links to readme
miguelgondu Oct 10, 2023
702f2d2
Add links and title to readme
miguelgondu Oct 10, 2023
82adf3f
Merge branch 'updating-readme-and-tdc-functions' of github.com:Machin…
miguelgondu Oct 10, 2023
7644d6f
Merge pull request #75 from MachineLearningLifeScience/updating-readm…
miguelgondu Oct 10, 2023
5efe6be
Scaffold RASP by bringing some of the code from their repository
miguelgondu Oct 10, 2023
fe6a2ba
Brings RaSP's code
miguelgondu Oct 11, 2023
e480df0
Lints the rasp files
miguelgondu Oct 11, 2023
e3c0ada
Scaffolds the data clean-up process of RaSP
miguelgondu Oct 16, 2023
622046d
Brings the old implementation of rasp, which doesn't need to compute …
miguelgondu Oct 17, 2023
219eb09
Lints the external code
miguelgondu Oct 17, 2023
e641722
Performs predictions using RaSP
miguelgondu Oct 17, 2023
56d47b8
Refactors the rasp interface
miguelgondu Oct 19, 2023
437f6cd
Implements a black box function for RaSP
miguelgondu Oct 24, 2023
11caa69
Implements a black box function for RaSP
miguelgondu Oct 24, 2023
b9fa69b
Runs linting
miguelgondu Oct 24, 2023
f3bebb7
Forgot linting of one file
miguelgondu Oct 24, 2023
30ff002
Implements the problem factory for RaSP
miguelgondu Oct 24, 2023
dea47d5
Makes sure that the position in the variant is accurate by relying on…
miguelgondu Oct 24, 2023
ffa60bb
Cleans up the warnings thrown by RaSP inference
miguelgondu Oct 25, 2023
9d510e4
Refactors the interface and model loading to have more representative…
miguelgondu Oct 25, 2023
2becc26
Adds better error handling and exceptions when running commands on th…
miguelgondu Oct 25, 2023
9473941
Implements RaSP for more than one wildtype
miguelgondu Oct 25, 2023
b3e8694
Uncomments foldx example and lints
miguelgondu Oct 25, 2023
5c25427
Adds an example of how to compute saturation mutagenesis using RaSP
miguelgondu Oct 26, 2023
85f2639
Implements downloading the cavity and downstream models from RaSPs re…
miguelgondu Oct 27, 2023
3aa48a9
Adds MD5 checksums to verify the integrity of the models we download
miguelgondu Oct 30, 2023
f871534
Adds a test for RaSP (which will get skipped in the action, unfortuna…
miguelgondu Oct 30, 2023
36b39f6
Updates the conda environment for RaSP
miguelgondu Oct 30, 2023
e5dcaf2
Uses pdb tools directly, instead of running a subprocess
miguelgondu Oct 30, 2023
3273c8f
Merge pull request #76 from MachineLearningLifeScience/feature-add-rasp
miguelgondu Oct 30, 2023
89ee670
Adds RaSP to testing
miguelgondu Oct 31, 2023
a905d2e
Adds forceful registration to the RaSP test
miguelgondu Oct 31, 2023
81f396e
Adds missing dependencies to poli__protein and poli__rasp envs
miguelgondu Nov 1, 2023
dff5441
Refactors tests, and removes RaSP from testing on dev
miguelgondu Nov 1, 2023
cd0cab0
Specifies the ignore paths as posargs in tox
miguelgondu Nov 1, 2023
c1985e6
Specifies a different configuration file for dev and master testing
miguelgondu Nov 1, 2023
c982a38
Specifies config file for linting in the github action
miguelgondu Nov 1, 2023
675646e
Removes the installation of the poli__rasp environment from tox dev
miguelgondu Nov 1, 2023
f785116
Incorporates these changes to testing in the documentation of how to …
miguelgondu Nov 1, 2023
7074477
Merge pull request #78 from MachineLearningLifeScience/feature-better…
miguelgondu Nov 1, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions .github/workflows/python-tox-testing-including-conda-on-master.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: Test (master, conda, python 3.9)

on:
pull_request:
branches:
- master

jobs:
build-linux:
runs-on: ubuntu-latest
strategy:
max-parallel: 5

steps:
- uses: actions/checkout@v3
- name: Set up Python 3.9
uses: actions/setup-python@v3
with:
python-version: '3.9'
- name: Add conda to system path
run: |
# $CONDA is an environment variable pointing to the root of the miniconda directory
echo $CONDA/bin >> $GITHUB_PATH
- name: Install dependencies
run: |
python -m pip install tox
- name: Test linting with tox
run: |
tox -c tox.master.ini -e lint
- name: Test poli-base with tox
run: |
tox -c tox.master.ini -e poli-base-py39
- name: Test poli-chem with tox
run: |
tox -c tox.master.ini -e poli-chem-py39
- name: Test poli-protein with tox
run: |
tox -c tox.master.ini -e poli-protein-py39
14 changes: 4 additions & 10 deletions .github/workflows/python-tox-testing-including-conda.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Test (conda, python 3.9)
name: Test (dev, conda, python 3.9)

on: [push]

Expand All @@ -23,13 +23,7 @@ jobs:
python -m pip install tox
- name: Test linting with tox
run: |
tox -e lint
- name: Test poli-base with tox
tox -c tox.dev.ini -e lint
- name: Test poli-base with tox (ignoring RaSP)
run: |
tox -e poli-base-py39
- name: Test poli-chem with tox
run: |
tox -e poli-chem-py39
- name: Test poli-protein with tox
run: |
tox -e poli-protein-py39
tox -c tox.dev.ini -e poli-base-py39 -- --ignore=src/poli/tests/registry/proteins/test_rasp.py
9 changes: 9 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,12 @@ src/poli/registered_objectives/*.sh

.vscode/
.DS_Store
oracle/

# Ignores MLFlow and WandB runs
mlruns/
wandb/

# Ignore temporary files
tmp/
src/poli/objective_repository/rasp/101m.pdb
47 changes: 47 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Contributing to `poli`

![Linting: black](https://img.shields.io/badge/Linting-black-black)
![Testing: pytest](https://img.shields.io/badge/Testing-pytest-blue)
![Testing: tox](https://img.shields.io/badge/Testing-tox-blue)
![Main branch: black](https://img.shields.io/badge/Pull_request_to-dev-blue)

This note details how to contribute to `poli`.

## Forking and making pull requests

The main development branch is called `dev`. To contribute, we recommend creating a fork of this repository and making changes on your version. Once you are ready to contribute, we expect you to lint and test.

## Linting your changes

We expect you to lint the code you write or modify using `black`.

```bash
pip install black
black ./path/to/files
```

## Testing your changes for `dev``

Since we are testing multiple conda environments, we settled for using a combination of `tox` and `pytest`.

```bash
pip install tox

# To test linting (from the root of the project)
tox -c tox.dev.ini -e lint

# To test in the base environment for poli
tox -c tox.dev.ini -e poli-base-py39
```

If you want to run tests in all environments, remove `-e poli-base-py39` and just run `tox`.

## More thorough testing

In many cases, testing with the instructions above should be enough. However, since we are dealing with creating conda environments, the definite test comes by building the Docker image specified in `Dockerfile.test`, and running it.

When contributing to the `@master` branch (i.e. to release), we will run these tests.

## Create a pull request to dev

Once all tests pass and you are ready to share your changes, create a pull request to the `dev` branch.
34 changes: 34 additions & 0 deletions Dockerfile.test
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# This dockerfile allows us to run the tests in a container
FROM --platform=linux/amd64 continuumio/anaconda3:latest

# Set working directory
WORKDIR /app

# Copying the files from the host to the container
COPY ./src /app/src
COPY ./pyproject.toml /app/
COPY ./setup.cfg /app/
COPY ./requirements.txt /app/
COPY ./requirements-dev.txt /app/
COPY ./tox.ini /app/

# Installing distutils
RUN apt-get update && \
apt-get install build-essential -y && \
apt-get install -y python3.9-distutils

# Installing python dependencies
RUN conda --version
RUN pip install -r requirements.txt
RUN pip install -r requirements-dev.txt

# Creating the relevant conda environments
# For chem
RUN conda env create --file src/poli/objective_repository/rdkit_qed/environment.yml

# For proteins
RUN conda env create --file src/poli/objective_repository/foldx_stability/environment.yml
RUN conda env create --file src/poli/objective_repository/rasp/environment.yml

# Running the tests
CMD ["tox"]
54 changes: 37 additions & 17 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,22 +1,18 @@
# Protein Objective Library (POLi)
# `poli`, a library for discrete objective functions

[![Testing (conda, python 3.9)](https://github.com/MachineLearningLifeScience/poli/actions/workflows/python-tox-testing-including-conda.yml/badge.svg)](https://github.com/MachineLearningLifeScience/poli/actions/workflows/python-tox-testing-including-conda.yml)

An easy-to-use, plug-and-play library to benchmark protein-related discrete optimization algorithms.
Primarily, this library provides a way to encapsulate objective functions and their dependencies.
The main benefit is that this allows to develop optimization algorithms that use (say) tensorflow without having to worry that the objective was written in (say) torch.
`poli` is an easy-to-use, plug-and-play library to query black-box functions in biology and cheminformatics. Examples include:
- Computing the **stability** of mutations from a wildtype protein (using `foldx`).
- Computing the **docking scores** of ligands to proteins (using [`pyscreener`](https://github.com/coleygroup/pyscreener) and [`pytdc`](https://tdcommons.ai/functions/oracles/)).

For any code written by other authors (whether objective function or algorithm) this library allows to benchmark and analyse it without too much interaction.

On purpose, logging is kept at the objective function side.
This allows easier benchmarking of algorithms from other authors.
Algorithm-specific logging can be done internally, on the site of the algorithm if necessary.
When dependencies get tough, this library provides a way to encapsulate objective functions into isolated `conda` environments. The main benefit is that this allows to develop optimization algorithms that use (say) tensorflow without having to worry about the specific dependencies of the objective function. Moreover, `poli` provides a way to inject logging into the objective function evaluations using observers.

## Basic usage

### Installation

Run the following from the main directory (where this README file is also located) to install the package in development mode (that is, modifications to the source code is directly visible to file importing it without the need for reinstallation).
Run the following from the main directory (where this README file is also located) to install the package in development mode (this way you could modify its contents and have these changes be reflected without having to re-install).
```
pip install -e .
```
Expand Down Expand Up @@ -52,16 +48,40 @@ for _ in range(5):

```

### Calling objective functions from the repository
### When you have the right dependencies...

If you have enough dependencies to run an objective function, it will become available. For example, try running `pip install rdkit selfies` followed by the `get_problems()` statement from above:

```bash
$ pip install rdkit selfies
$ python -c "from poli.core.registry import get_problems ; print(get_problems())"
['aloha', 'rdkit_logp', 'rdkit_qed', 'white_noise']
```

Now that both `rdkit` and `selfies` are in the current environment, problems like computing `logp` and `qed` of SELFIES or SMILES strings become available.

### Calling objective functions in isolated enviroments

As you might have noticed, you can get a list of the registered problems using the `get_problems` method inside `poli.core.registry`. You can also get a list of objective functions available for installing/registration using `from poli.objective_repository import AVAILABLE_PROBLEM_FACTORIES`:
To get a list of all avilable objective functions, you can pass the `include_repository=True` flag to `get_problems`:

```bash
$ python -c "from poli.objective_repository import AVAILABLE_PROBLEM_FACTORIES ; print(AVAILABLE_PROBLEM_FACTORIES)"
'{"white_noise": <WhiteNoiseProblemFactory(L=inf)>, ...}'
$ python -c "from poli.core.registry import get_problems ; print(get_problems(include_repository=True))"
['aloha', 'drd3_docking', 'foldx_sasa', 'foldx_stability', ..., 'white_noise']
```

If the function isn't there, you may:
- Install all the required dependencies for running the file. Check the relevant environment under `poli/objective_repository/problem_name/environment.yml`.
- Implement the problem yourself! An example of how to do this can be found in `poli/examples/a_simple_objective_function_registration`.
**Most of these objective functions can be run out-of-the-box** in isolated enviroments. For example, consider computing the synthetic accessibility of a molecule using `pytdc`. This problem is called `sa_tdc` in `poli`, and can easily be run without having the right dependencies installed:

```python
from poli import objective_factory
import numpy as np

problem_info, f, x0, y0, run_info = objective_factory.create(
name="sa_tdc",
force_register=True,
string_representation="SELFIES",
)

x = np.array([["[C]", "[C]", "[C]"]])
print(f"f({x}) = {f(x)}")

```
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ def get_setup_information(self) -> ProblemSetupInformation:
alphabet=alphabet,
)

def create(self, seed: int = 0) -> Tuple[AbstractBlackBox, np.ndarray, np.ndarray]:
def create(
self, seed: int = None, **kwargs
) -> Tuple[AbstractBlackBox, np.ndarray, np.ndarray]:
problem_info = self.get_setup_information()
f = OurAlohaBlackBox(info=problem_info)
x0 = np.array([["A", "L", "O", "O", "F"]])
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
"""
This script uses the wandb observer to log some examples of the rdkit_qed objective function.

To run this example, you will need to install wandb:

pip install wandb
"""

from pathlib import Path

import numpy as np

from poli import objective_factory

from wandb_observer import WandbObserver

THIS_DIR = Path(__file__).parent.resolve()

if __name__ == "__main__":
# Defining the observer
observer = WandbObserver()

# Initializing a QED objective function.
alphabet = ["", "[C]", "..."]
problem_info, f, x0, y0, run_info = objective_factory.create(
name="rdkit_qed",
observer=observer,
alphabet=alphabet,
string_representation="SELFIES",
caller_info={"run_id": None, "experiment_id": None},
)

# Logging some examples
# The observer will register each call to f.
f(np.array([["[C]", "[C]"]]))
f(np.array([["[C]", "[C]", "[C]"]]))
f(np.array([["[C]", "[C]", "[C]", "[C]"]]))

# Finishing the observer, which will log a table that's
# being maintained.
observer.finish()
54 changes: 54 additions & 0 deletions examples/adding_a_wandb_observer/wandb_observer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""A simple example of how to log objective function calls using wandb.

To run this example, you will need to install wandb:

pip install wandb
"""

import numpy as np
from poli.core.problem_setup_information import ProblemSetupInformation
import wandb

from poli.core.util.abstract_observer import AbstractObserver


class WandbObserver(AbstractObserver):
def __init__(self) -> None:
# Log into wandb
wandb.login()

# Some variables to keep track of the run
self.step = 0
self.x_table = wandb.Table(columns=["step", "x", "y"])
super().__init__()

def initialize_observer(
self,
problem_setup_info: ProblemSetupInformation,
caller_info: object,
x0: np.ndarray,
y0: np.ndarray,
seed: int,
) -> object:
wandb.init(
config={
"name": problem_setup_info.name,
"max_sequence_length": problem_setup_info.max_sequence_length,
"alphabet": problem_setup_info.alphabet,
"x0": x0,
"y0": y0,
"seed": seed,
},
)

def observe(self, x: np.ndarray, y: np.ndarray, context=None) -> None:
for x_i, y_i in zip(x.tolist(), y.tolist()):
self.x_table.add_data(self.step, "".join(x_i), y_i)

wandb.log({"table of sequences": self.x_table})
wandb.log({"y": y}, step=self.step)

self.step += 1

def finish(self) -> None:
wandb.finish()
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
"""
This script implements an example of how to use a simple
MLFlow observer (implemented in ./mlflow_observer.py). Running
this script will create a new experiment in ./mlruns.

To run this example, you will need to install mlflow:

pip install mlflow

To check its results, you will need to start a ui:

mlflow ui --backend-store-uri ./mlruns
"""

from pathlib import Path

import numpy as np

from poli import objective_factory

from mlflow_observer import MlFlowObserver

THIS_DIR = Path(__file__).parent.resolve()

if __name__ == "__main__":
# Defining the observer
TRACKING_URI = THIS_DIR / "mlruns"
observer = MlFlowObserver(tracking_uri=TRACKING_URI)

# Initializing a logP objective function.
alphabet = ["", "[C]", "..."]
problem_info, f, x0, y0, run_info = objective_factory.create(
name="rdkit_logp",
observer=observer,
alphabet=alphabet,
string_representation="SELFIES",
caller_info={"run_id": None, "experiment_id": None},
)

# Logging some examples
# The observer will register each call to f.
f(np.array([["[C]", "[C]"]]))
f(np.array([["[C]", "[C]", "[C]"]]))
f(np.array([["[C]", "[C]", "[C]", "[C]"]]))

# Finishing the observer, which will close the MLFlow run.
observer.finish()
Loading