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

Create Gradio-based GUI #589

Open
wants to merge 129 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
129 commits
Select commit Hold shift + click to select a range
c9c783c
initial commit
MilesCranmer Jun 11, 2022
0732790
Add PySR as pip requirement
MilesCranmer Jun 11, 2022
5720305
Add Julia package file
MilesCranmer Jun 11, 2022
9162188
Add pandas requirement
MilesCranmer Jun 11, 2022
c6a43c4
Add app to fit PySR model
MilesCranmer Jun 11, 2022
edbcfa6
Install PyCall.jl with other python instance
MilesCranmer Jun 11, 2022
34cffe9
Add file to generate example data
MilesCranmer Jun 22, 2022
9cee10c
Add gitignore
MilesCranmer Jun 22, 2022
222fbf0
Init gradio version
MilesCranmer Jun 22, 2022
f072863
Working app?
MilesCranmer Jun 22, 2022
460af25
Output more useful errors
MilesCranmer Jun 22, 2022
0cd6a71
Better labels
MilesCranmer Jun 22, 2022
d6fc94c
Update backend
MilesCranmer Jun 22, 2022
8614da9
Fix gradio issues
MilesCranmer Jun 22, 2022
3535667
Update sdk version
MilesCranmer Jun 22, 2022
454ec0a
Fix use of API
MilesCranmer Jun 22, 2022
dc554ea
Change title of app
MilesCranmer Jun 22, 2022
cc248dd
Bump pysr version
MilesCranmer Jun 22, 2022
e69aea3
More helpful error messages
MilesCranmer Jun 22, 2022
71ed397
Manually load julia
MilesCranmer Jun 22, 2022
1049889
More helpful error message for julia install
MilesCranmer Jun 22, 2022
3c09196
Try to install Julia within app
MilesCranmer Jun 22, 2022
48482f0
Deleted wrong file
MilesCranmer Jun 22, 2022
dcd98be
Fix pathname to Julia install
MilesCranmer Jun 22, 2022
e8191c4
Fix issues with not identifying folder
MilesCranmer Jun 22, 2022
0b70475
Fix path to Julia
MilesCranmer Jun 22, 2022
02c14cb
Protection against segfaults
MilesCranmer Jun 22, 2022
ad955c1
Run pysr in secondary instance
MilesCranmer Jun 22, 2022
7078b01
Avoid jill.sh to install julia
MilesCranmer Jun 22, 2022
fd867c5
Back to jill.sh
MilesCranmer Jun 22, 2022
1068831
Automatically install PySR at init
MilesCranmer Jun 22, 2022
a1300ad
Correct arguments
MilesCranmer Jun 22, 2022
deeb73e
Prevent quotes being removed from list
MilesCranmer Jun 22, 2022
c56b886
Fix error with not printing to error log
MilesCranmer Jun 22, 2022
fadaa8d
More warnings before running
MilesCranmer Jun 22, 2022
48aee58
Don't print progress when running
MilesCranmer Jun 22, 2022
458f2f7
Add more unary operators
MilesCranmer Jun 22, 2022
bd3106e
Extended app description
MilesCranmer Jun 22, 2022
a704d53
Output parameters for local runs
MilesCranmer Jun 22, 2022
6644c43
Enable adjustable maxsize
MilesCranmer Jun 22, 2022
ea8fece
Fix slider to be int steps
MilesCranmer Jun 22, 2022
59a445b
Fix issue with col not found
MilesCranmer Jun 22, 2022
efb57c1
Fix order of maxsize parameter
MilesCranmer Jun 22, 2022
3dc1350
More helpful error message with missing columns
MilesCranmer Jun 22, 2022
81b651b
Better description for running locally
MilesCranmer Jun 22, 2022
c3d41aa
Update PySR version
MilesCranmer Nov 16, 2022
5a4bba9
Update requirements.txt
MilesCranmer Nov 16, 2022
ee0ed3e
Fix path to Julia
MilesCranmer Nov 16, 2022
65baea3
Update to PySR 0.18.1
MilesCranmer Mar 28, 2024
13219e6
Move everything to app.py
MilesCranmer Mar 28, 2024
8ec0807
Add custom docker file
MilesCranmer Mar 28, 2024
126dce4
initial commit
Mar 28, 2024
27373bb
Add application file
MilesCranmer Mar 28, 2024
5fed067
Try to fix gradio app
MilesCranmer Mar 28, 2024
24ea219
Install juliaup
MilesCranmer Mar 28, 2024
a0ac113
Link julia to local bin
MilesCranmer Mar 28, 2024
67597d0
Fix path issue
MilesCranmer Mar 28, 2024
c9ef87b
Copy from julia base container instead
MilesCranmer Mar 28, 2024
375d20a
Bump Python to 3.12
MilesCranmer Mar 28, 2024
b31a378
Fix python runner
MilesCranmer Mar 28, 2024
4b720d9
Again try to fix
MilesCranmer Mar 28, 2024
d50f3f0
Up dockerfile
MilesCranmer Mar 28, 2024
5ed4b11
Blank
MilesCranmer Mar 28, 2024
f6dc423
Up dockerfile
MilesCranmer Mar 28, 2024
91ce4dd
Fix
MilesCranmer Mar 28, 2024
801ce9c
Fix length limit
MilesCranmer Mar 28, 2024
a9e19e6
Install Bumper as well
MilesCranmer Mar 28, 2024
27d64c8
Fix up docker
MilesCranmer Mar 28, 2024
4de6d3f
Set correct env variables for binding to HF
MilesCranmer Mar 28, 2024
ce963bc
Try to show progress in logs
MilesCranmer Mar 28, 2024
d39a013
Add timeout
MilesCranmer Mar 28, 2024
08f8ef7
Fix operator selection
MilesCranmer Mar 28, 2024
88a78a4
Black formatting
MilesCranmer Mar 28, 2024
73042d9
Add test data generator to app
MilesCranmer Mar 28, 2024
46fdaa6
Automatically plot test data
MilesCranmer Mar 28, 2024
e1cf25c
Instructions in upload tab
MilesCranmer Mar 28, 2024
dd65136
Refactor app
MilesCranmer Mar 28, 2024
c353ada
Clean up output col
MilesCranmer Mar 28, 2024
5a5a76f
Turn off multithreading
MilesCranmer Mar 28, 2024
bb76c1f
Only run PySR in another process
MilesCranmer Mar 28, 2024
fea9443
Plotting of pareto front
MilesCranmer Mar 28, 2024
4eac491
Improve formatting of plot
MilesCranmer Mar 28, 2024
758e952
Make nicer plot for example data
MilesCranmer Mar 28, 2024
2681c82
Turn multithreading back on
MilesCranmer Mar 28, 2024
f751163
Fix formatting of data gen tab
MilesCranmer Mar 28, 2024
9d6017e
Add setting for plot update rate
MilesCranmer Mar 28, 2024
8a2bd53
Add more advanced settings
MilesCranmer Mar 28, 2024
e487754
Typo
MilesCranmer Mar 28, 2024
0dc382d
More operators
MilesCranmer Mar 28, 2024
d3c4f72
Remove huge number of operators
MilesCranmer Mar 28, 2024
63ae9cd
Add pre-commit config
MilesCranmer Mar 28, 2024
146981e
Start prediction plot
MilesCranmer Mar 30, 2024
e72c14a
Download IBM Plex Mono for GUI
MilesCranmer Mar 30, 2024
8192cb8
Add missing readme
MilesCranmer Mar 30, 2024
fc128f4
Move HF contents to main readme
MilesCranmer Mar 30, 2024
896066b
Bump jl version in container
MilesCranmer Mar 30, 2024
0430395
Have GUI be default mode of dockerfile
MilesCranmer Mar 30, 2024
a300bd3
Remove example data generator
MilesCranmer Mar 30, 2024
754d45b
Remove unused parts of gui
MilesCranmer Mar 30, 2024
2b187fd
Update gui requirements
MilesCranmer Mar 30, 2024
177ae5a
Minimize requirements
MilesCranmer Mar 30, 2024
2665e2b
Clean up how fonts are set
MilesCranmer Mar 30, 2024
6cb0a58
Set versions for matplotlib and gradio
MilesCranmer Mar 30, 2024
bccdea1
Fix permissions issue in docker GUI
MilesCranmer Mar 30, 2024
7f0b93d
Add virtualenv to PATH
MilesCranmer Mar 31, 2024
9fa2182
Refactor GUI to multiple files
MilesCranmer Apr 1, 2024
519fcb9
Move more parts to other files
MilesCranmer Apr 1, 2024
86e9755
Integrate gui in main codebase
MilesCranmer Apr 1, 2024
985f8fa
Revert "Integrate gui in main codebase"
MilesCranmer Apr 1, 2024
967d63f
Disable refresh animation in plots
MilesCranmer Apr 1, 2024
0cd448a
Attempt to make PySR process a daemon
MilesCranmer Apr 1, 2024
c1a4fec
Better names for plotting functions
MilesCranmer Apr 2, 2024
c52d151
Standardize figure settings
MilesCranmer Apr 2, 2024
7701310
Allow control of file loading verbosity
MilesCranmer Apr 2, 2024
fd28328
wip on predictions from equations
MilesCranmer Apr 2, 2024
a2492c3
Better support for live reading and predictions
MilesCranmer Apr 2, 2024
84b46ac
Working prediction plot
MilesCranmer Apr 2, 2024
ef7aada
style(gui): add default gui for config
MilesCranmer Apr 17, 2024
a206d6a
refactor(gui): gradio to use object oriented wrapper
MilesCranmer Apr 17, 2024
89fd807
refactor(gui): fix issues with big refactor
MilesCranmer Apr 17, 2024
2608088
refactor(gui): launch code
MilesCranmer Apr 17, 2024
2139b62
refactor(gui): remove redundant Row
MilesCranmer Apr 19, 2024
06338fc
refactor(gui): avoid compiling from two processes at once
MilesCranmer Apr 19, 2024
ed67e70
fix(gui): add all gui files to Dockerfile
MilesCranmer Apr 19, 2024
28639ea
feat(gui): add status messages
MilesCranmer Apr 19, 2024
b58f1db
feat(gui): reset plot at run
MilesCranmer Apr 19, 2024
b955f86
feat(gui): add stop button
MilesCranmer Apr 19, 2024
e4dfed6
refactor(gui): put buttons in same row
MilesCranmer Apr 19, 2024
0f7799e
fix(gui): kill processes not terminate
MilesCranmer Apr 19, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
57 changes: 42 additions & 15 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# This builds a dockerfile containing a working copy of PySR
# with all pre-requisites installed.

ARG JLVERSION=1.9.4
ARG JLVERSION=1.10.0
ARG PYVERSION=3.11.6
ARG BASE_IMAGE=bullseye

Expand All @@ -12,28 +12,55 @@ FROM python:${PYVERSION}-${BASE_IMAGE}
COPY --from=jl /usr/local/julia /usr/local/julia
ENV PATH="/usr/local/julia/bin:${PATH}"

# Install IPython and other useful libraries:
RUN pip install --no-cache-dir ipython matplotlib
# Install font used for GUI
RUN mkdir -p /usr/local/share/fonts/IBM_Plex_Mono && \
curl -L https://github.com/IBM/plex/releases/download/v6.4.0/IBM-Plex-Mono.zip -o /tmp/IBM_Plex_Mono.zip && \
unzip /tmp/IBM_Plex_Mono.zip -d /usr/local/share/fonts/IBM_Plex_Mono && \
rm /tmp/IBM_Plex_Mono.zip
RUN fc-cache -f -v

WORKDIR /pysr
# Set up a new user named "user" with user ID 1000
RUN useradd -m -u 1000 user
USER user
WORKDIR /home/user/
ENV HOME=/home/user
ENV PATH=/home/user/.local/bin:$PATH

# Caches install (https://stackoverflow.com/questions/25305788/how-to-avoid-reinstalling-packages-when-building-docker-image-for-python-project)
ADD ./requirements.txt /pysr/requirements.txt
RUN pip3 install --no-cache-dir -r /pysr/requirements.txt
RUN python -m venv $HOME/.venv

# Install PySR:
# We do a minimal copy so it doesn't need to rerun at every file change:
ADD ./pyproject.toml /pysr/pyproject.toml
ADD ./setup.py /pysr/setup.py
ADD ./pysr /pysr/pysr
RUN pip3 install --no-cache-dir .
ENV PYTHON="${HOME}/.venv/bin/python"
ENV PIP="${PYTHON} -m pip"
ENV PATH="${HOME}/.venv/bin:${PATH}"

WORKDIR $HOME/pysr

# Install all requirements, and then PySR itself
COPY --chown=user ./requirements.txt $HOME/pysr/requirements.txt
RUN $PIP install --no-cache-dir -r $HOME/pysr/requirements.txt

COPY --chown=user ./gui/requirements.txt $HOME/pysr/gui/requirements.txt
RUN $PIP install --no-cache-dir -r $HOME/pysr/gui/requirements.txt

COPY --chown=user ./pyproject.toml $HOME/pysr/pyproject.toml
COPY --chown=user ./setup.py $HOME/pysr/setup.py
COPY --chown=user ./pysr $HOME/pysr/pysr
RUN $PIP install --no-cache-dir .

# Install Julia pre-requisites:
RUN python3 -c 'import pysr'
RUN $PYTHON -c 'import pysr'

COPY --chown=user ./gui/*.py $HOME/pysr/gui/

EXPOSE 7860
ENV GRADIO_ALLOW_FLAGGING=never \
GRADIO_NUM_PORTS=1 \
GRADIO_SERVER_NAME=0.0.0.0 \
GRADIO_THEME=huggingface \
SYSTEM=spaces

# metainformation
LABEL org.opencontainers.image.authors = "Miles Cranmer"
LABEL org.opencontainers.image.source = "https://github.com/MilesCranmer/PySR"
LABEL org.opencontainers.image.licenses = "Apache License 2.0"

CMD ["ipython"]
CMD ["/home/user/.venv/bin/python", "/home/user/pysr/gui/app.py"]
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,13 @@
---
title: PySR
emoji: 🌍
colorFrom: green
colorTo: indigo
sdk: docker
pinned: false
license: apache-2.0
---

[//]: # (Logo:)

<div align="center">
Expand Down
300 changes: 300 additions & 0 deletions gui/app.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,300 @@
from collections import OrderedDict

import gradio as gr
import numpy as np
from data import TEST_EQUATIONS
from gradio.components.base import Component
from plots import plot_example_data, plot_pareto_curve
from processing import processing, stop


class ExampleData:
def __init__(self, demo: gr.Blocks) -> None:
with gr.Column(scale=1):
self.example_plot = gr.Plot()
with gr.Column(scale=1):
self.test_equation = gr.Radio(
TEST_EQUATIONS, value=TEST_EQUATIONS[0], label="Test Equation"
)
self.num_points = gr.Slider(
minimum=10,
maximum=1000,
value=200,
label="Number of Data Points",
step=1,
)
self.noise_level = gr.Slider(
minimum=0, maximum=1, value=0.05, label="Noise Level"
)
self.data_seed = gr.Number(value=0, label="Random Seed")

# Set up plotting:

eqn_components = [
self.test_equation,
self.num_points,
self.noise_level,
self.data_seed,
]
for eqn_component in eqn_components:
eqn_component.change(
plot_example_data,
eqn_components,
self.example_plot,
show_progress=False,
)

demo.load(plot_example_data, eqn_components, self.example_plot)


class UploadData:
def __init__(self) -> None:
self.file_input = gr.File(label="Upload a CSV File")
self.label = gr.Markdown(
"The rightmost column of your CSV file will be used as the target variable."
)


class Data:
def __init__(self, demo: gr.Blocks) -> None:
with gr.Tab("Example Data"):
self.example_data = ExampleData(demo)
with gr.Tab("Upload Data"):
self.upload_data = UploadData()


class BasicSettings:
def __init__(self) -> None:
self.binary_operators = gr.CheckboxGroup(
choices=["+", "-", "*", "/", "^", "max", "min", "mod", "cond"],
label="Binary Operators",
value=["+", "-", "*", "/"],
)
self.unary_operators = gr.CheckboxGroup(
choices=[
"sin",
"cos",
"tan",
"exp",
"log",
"square",
"cube",
"sqrt",
"abs",
"erf",
"relu",
"round",
"sign",
],
label="Unary Operators",
value=["sin"],
)
self.niterations = gr.Slider(
minimum=1,
maximum=1000,
value=40,
label="Number of Iterations",
step=1,
)
self.maxsize = gr.Slider(
minimum=7,
maximum=100,
value=20,
label="Maximum Complexity",
step=1,
)
self.parsimony = gr.Number(
value=0.0032,
label="Parsimony Coefficient",
)


class AdvancedSettings:
def __init__(self) -> None:
self.populations = gr.Slider(
minimum=2,
maximum=100,
value=15,
label="Number of Populations",
step=1,
)
self.population_size = gr.Slider(
minimum=2,
maximum=1000,
value=33,
label="Population Size",
step=1,
)
self.ncycles_per_iteration = gr.Number(
value=550,
label="Cycles per Iteration",
)
self.elementwise_loss = gr.Radio(
["L2DistLoss()", "L1DistLoss()", "LogitDistLoss()", "HuberLoss()"],
value="L2DistLoss()",
label="Loss Function",
)
self.adaptive_parsimony_scaling = gr.Number(
value=20.0,
label="Adaptive Parsimony Scaling",
)
self.optimizer_algorithm = gr.Radio(
["BFGS", "NelderMead"],
value="BFGS",
label="Optimizer Algorithm",
)
self.optimizer_iterations = gr.Slider(
minimum=1,
maximum=100,
value=8,
label="Optimizer Iterations",
step=1,
)
self.batching = gr.Checkbox(
value=False,
label="Batching",
)
self.batch_size = gr.Slider(
minimum=2,
maximum=1000,
value=50,
label="Batch Size",
step=1,
)


class GradioSettings:
def __init__(self) -> None:
self.plot_update_delay = gr.Slider(
minimum=1,
maximum=100,
value=3,
label="Plot Update Delay",
)
self.force_run = gr.Checkbox(
value=False,
label="Ignore Warnings",
)


class Settings:
def __init__(self):
with gr.Tab("Basic Settings"):
self.basic_settings = BasicSettings()
with gr.Tab("Advanced Settings"):
self.advanced_settings = AdvancedSettings()
with gr.Tab("Gradio Settings"):
self.gradio_settings = GradioSettings()


class Results:
def __init__(self):
with gr.Tab("Pareto Front"):
self.pareto = gr.Plot()
with gr.Tab("Predictions"):
self.predictions_plot = gr.Plot()

self.df = gr.Dataframe(
headers=["complexity", "loss", "equation"],
datatype=["number", "number", "str"],
wrap=True,
column_widths=[75, 75, 200],
interactive=False,
)

self.messages = gr.Textbox(label="Messages", value="", interactive=False)


def flatten_attributes(
component_group, absolute_name: str, d: OrderedDict
) -> OrderedDict:
if not hasattr(component_group, "__dict__"):
return d

for name, elem in component_group.__dict__.items():
new_absolute_name = absolute_name + "." + name
if name.startswith("_"):
# Private attribute
continue
elif elem in d.values():
# Don't duplicate any tiems
continue
elif isinstance(elem, Component):
# Only add components to dict
d[new_absolute_name] = elem
else:
flatten_attributes(elem, new_absolute_name, d)

return d


class AppInterface:
def __init__(self, demo: gr.Blocks) -> None:
with gr.Row():
with gr.Column(scale=2):
with gr.Row():
self.data = Data(demo)
with gr.Row():
self.settings = Settings()
with gr.Column(scale=2):
self.results = Results()
with gr.Row():
with gr.Column(scale=1):
self.stop = gr.Button(value="Stop")
with gr.Column(scale=1, min_width=200):
self.run = gr.Button()

# Update plot when dataframe is updated:
self.results.df.change(
plot_pareto_curve,
inputs=[self.results.df, self.settings.basic_settings.maxsize],
outputs=[self.results.pareto],
show_progress=False,
)

ignore = ["df", "predictions_plot", "pareto", "messages"]
self.run.click(
create_processing_function(self, ignore=ignore),
inputs=[
v
for k, v in flatten_attributes(self, "interface", OrderedDict()).items()
if last_part(k) not in ignore
],
outputs=[
self.results.df,
self.results.predictions_plot,
self.results.messages,
],
show_progress=True,
)
self.stop.click(stop)


def last_part(k: str) -> str:
return k.split(".")[-1]


def create_processing_function(interface: AppInterface, ignore=[]):
d = flatten_attributes(interface, "interface", OrderedDict())
keys = [k for k in map(last_part, d.keys()) if k not in ignore]
_, idx, counts = np.unique(keys, return_index=True, return_counts=True)
if np.any(counts > 1):
raise AssertionError("Bad keys: " + ",".join(np.array(keys)[idx[counts > 1]]))

def f(*components):
n = len(components)
assert n == len(keys)
for output in processing(**{keys[i]: components[i] for i in range(n)}):
yield output

return f


def main():
with gr.Blocks(theme="default") as demo:
_ = AppInterface(demo)
demo.launch(debug=True)


if __name__ == "__main__":
main()