Skip to content

Commit

Permalink
Merge pull request #58 from oulianov/main
Browse files Browse the repository at this point in the history
Adds deployment instructions and fix errors in embedding_cpsat example app
  • Loading branch information
d-krupke authored Jan 3, 2025
2 parents 290a407 + 6838981 commit b288da8
Show file tree
Hide file tree
Showing 5 changed files with 1,162 additions and 5 deletions.
37 changes: 35 additions & 2 deletions examples/embedding_cpsat/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,38 @@
# Embedding CP-SAT in an Application

If you want to embed CP-SAT in your application for potentially long-running optimization tasks, you can utilize callbacks to provide users with progress updates and potentially interrupt the process early. However, one issue is that the application can only react during the callback. Since the callback is not always called frequently, this may lead to problematic delays, making it unsuitable for graphical user interfaces (GUIs) or application programming interfaces (APIs).
If you want to embed CP-SAT in your application for potentially long-running optimization tasks, you can use callbacks to provide users with progress updates and potentially interrupt the process early. However, one issue is that the application can only react during the callback. Since the callback is not always called frequently, this may lead to problematic delays, making it unsuitable for graphical user interfaces (GUIs) or application programming interfaces (APIs).

An alternative is to let the solver run in a separate process and communicate with it using a pipe. This approach allows the solver to be interrupted at any time, enabling the application to react immediately. Python's multiprocessing module provides reasonably simple tools to achieve this. The following example showcases such an approach.
An alternative is to let the solver **run in a separate process** and communicate with it using a pipe. This approach allows the solver to be interrupted at any time, enabling the application to react immediately. Python's multiprocessing module provides reasonably simple tools to achieve this. The following example showcases such an approach.

## Installation

This demo is a streamlit app showcasing multiprocessing. The simplest way to run it on your computer is to use [uv python package manager](https://github.com/astral-sh/uv).

1. Install uv.

```bash
# On macOS and Linux.
curl -LsSf https://astral.sh/uv/install.sh | sh
```

```bash
# On Windows.
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
```

2. Run the app with this command.

```bash
uv run streamlit run app.py
```

3. Open your browser and try the app (the default URL is [http://localhost:8501](http://localhost:8501))

## Deployment

The easiest way to deploy this app to the internet for free is to use the [Streamlit community cloud](https://streamlit.io/cloud).

1. Make sure you forked this repository
2. Register to Streamlit cloud using your git account
3. Create a new Streamlit cloud app. Enter the url to your git repository and the path to the `app.py` file : `examples/embedding_cpsat/app.py`
4. You'll be redirected to a website with your app. You can now share this URL with other people.
23 changes: 20 additions & 3 deletions examples/embedding_cpsat/app.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,17 +81,34 @@ def calculate_progress(lower_bound, upper_bound):
# Title of the app
st.title("TSP Solving with CP-SAT")

st.markdown("""
This app generates a random number of points and solves the [Traveling Salesman Problem (TSP)](https://simple.wikipedia.org/wiki/Travelling_salesman_problem) using the [CP-SAT solver](https://developers.google.com/optimization/cp?hl=fr).
""")

# Add a field for the number of points and generate/abort buttons with improved layout
st.sidebar.header("Configuration")
num_points = st.sidebar.number_input("Number of points", min_value=2, value=100)

generate_button = st.sidebar.button("Generate and run")
interrupt_button = st.sidebar.button("Abort")

# Reference
st.sidebar.markdown(
"""
This app is part of a CP-SAT teaching series. It shows how to embed the CP-SAT solver in an app with multiprocessing.
Learn more about solving hard combinatorial problems [with the CP-SAT primer.](https://github.com/d-krupke/cpsat-primer/tree/main?tab=readme-ov-file)
"""
)

plot_placeholder = st.empty()
if "plot" not in st.session_state:
st.session_state.plot = None
plot_placeholder.pyplot(st.session_state.plot)
if st.session_state.plot is not None:
plot_placeholder.pyplot(st.session_state.plot)
else:
# Display instructions
st.info("Click 'Generate and run' on the sidebar to start the solver.")

lb_ub_placeholder = st.empty()
if "lb_ub" not in st.session_state:
Expand All @@ -103,7 +120,7 @@ def calculate_progress(lower_bound, upper_bound):
log_placeholder = st.empty()
if "log_text" not in st.session_state:
st.session_state.log_text = ""
log_placeholder.text(st.session_state.log_text)
log_placeholder.code(st.session_state.log_text, language="text")


if generate_button:
Expand Down Expand Up @@ -135,7 +152,7 @@ def calculate_progress(lower_bound, upper_bound):
logs = solver_process.get_log()
if logs:
st.session_state.log_text += "\n".join(logs) + "\n"
log_placeholder.text(st.session_state.log_text)
log_placeholder.code(st.session_state.log_text, language="text")

progress = calculate_progress(lower_bound, upper_bound)
progress_bar.progress(progress)
Expand Down
12 changes: 12 additions & 0 deletions examples/embedding_cpsat/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
[project]
name = "embedding-cpsat"
version = "0.1.0"
description = "An example of embedding CPSAT with multiprocessing in a streamlit app"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"matplotlib>=3.10.0",
"ortools>=9.11.4210",
"pydantic>=2.10.4",
"streamlit>=1.41.1",
]
1 change: 1 addition & 0 deletions examples/embedding_cpsat/solver_process.py
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,7 @@ def start(self):

def interrupt(self):
"""Interrupts the optimization process."""
self.process.join(timeout=1)
if self.process.pid and self.process.is_alive():
os.kill(self.process.pid, signal.SIGINT)

Expand Down
Loading

0 comments on commit b288da8

Please sign in to comment.