From cd84f23b29688957002bdef5e597be60e9a8cbdd Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:26:06 -0700
Subject: [PATCH 01/17] add readthedocs configuration file
---
.readthedocs.yaml | 18 ++++++++++++++++++
1 file changed, 18 insertions(+)
create mode 100644 .readthedocs.yaml
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
new file mode 100644
index 0000000..8a8d931
--- /dev/null
+++ b/.readthedocs.yaml
@@ -0,0 +1,18 @@
+# Read the Docs configuration file for MkDocs projects
+# See https://docs.readthedocs.io/en/stable/config-file/v2.html for details
+
+# Required
+version: 2
+
+ # Set the version of Python and other tools you might need
+build: os: ubuntu-22.04
+ tools:
+ python: "3.10"
+
+mkdocs:
+ configuration: mkdocs.yml
+
+# # Optionally declare the Python requirements required to build your docs
+# python:
+# install:
+# - requirements: docs/requirements.txt
From b9a3bee2a26d267b6eba9cf42780634b05d0fd91 Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:32:08 -0700
Subject: [PATCH 02/17] add readthedocs configuration file
---
.readthedocs.yaml | 9 +++++----
1 file changed, 5 insertions(+), 4 deletions(-)
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index 8a8d931..bdb2a5d 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -4,15 +4,16 @@
# Required
version: 2
- # Set the version of Python and other tools you might need
-build: os: ubuntu-22.04
+# Set the version of Python and other tools you might need
+build:
+ os: ubuntu-22.04
tools:
- python: "3.10"
+ python: "3.12"
mkdocs:
configuration: mkdocs.yml
-# # Optionally declare the Python requirements required to build your docs
+# Optionally declare the Python requirements required to build your docs
# python:
# install:
# - requirements: docs/requirements.txt
From 09f964249c1b9daa45d501963007667b542110e1 Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:37:08 -0700
Subject: [PATCH 03/17] update material theme
---
.readthedocs.yaml | 6 +++---
docs/requirements.txt | 1 +
2 files changed, 4 insertions(+), 3 deletions(-)
create mode 100644 docs/requirements.txt
diff --git a/.readthedocs.yaml b/.readthedocs.yaml
index bdb2a5d..015eb5d 100644
--- a/.readthedocs.yaml
+++ b/.readthedocs.yaml
@@ -14,6 +14,6 @@ mkdocs:
configuration: mkdocs.yml
# Optionally declare the Python requirements required to build your docs
-# python:
-# install:
-# - requirements: docs/requirements.txt
+python:
+ install:
+ - requirements: docs/requirements.txt
diff --git a/docs/requirements.txt b/docs/requirements.txt
new file mode 100644
index 0000000..4c8f017
--- /dev/null
+++ b/docs/requirements.txt
@@ -0,0 +1 @@
+mkdocs-material
From 4d33b133aea787c65c5575c987ef428fbe43918b Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:38:58 -0700
Subject: [PATCH 04/17] add mkdocstrings
---
docs/requirements.txt | 1 +
1 file changed, 1 insertion(+)
diff --git a/docs/requirements.txt b/docs/requirements.txt
index 4c8f017..9283a65 100644
--- a/docs/requirements.txt
+++ b/docs/requirements.txt
@@ -1 +1,2 @@
mkdocs-material
+mkdocstrings-python
From 116a15af0c2d1b4b200fad95c86fa33d70027751 Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:43:32 -0700
Subject: [PATCH 05/17] update installation instructions
---
docs/contributing.md | 11 ++++++++---
docs/installing.md | 6 +++---
2 files changed, 11 insertions(+), 6 deletions(-)
diff --git a/docs/contributing.md b/docs/contributing.md
index 5668ea0..32049a1 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -3,17 +3,22 @@
If you want to modify FLORAS or contribute, you can also install it directly from source. We are using pdm to manage the dependencies.
```
pip install pdm
-git clone url
+git clone https://github.com/tulip-control/flowsynth
```
Navigate to the repo and run to install the FLORAS and all required dependencies:
```
pdm install
```
-Next, enter the virtual environment created by pdm:
+Next, install spot by running
+```
+pdm run python get_spot.py
+```
+To enter the virtual environment created by pdm:
```
$(pdm venv activate)
```
-New dependencies can be added by running:
+
+If you need to add dependencies you can do that by running:
```
pdm add your_dependency_here
```
diff --git a/docs/installing.md b/docs/installing.md
index 03fe616..8ac9ec9 100644
--- a/docs/installing.md
+++ b/docs/installing.md
@@ -3,13 +3,13 @@
FLORAS requires `Python>=3.10` and a C++17-compliant compiler (for example `g++>=7.0` or `clang++>=5.0`).
You can check the versions by running `python --version` and `gcc --version`.
-### From Pypi
+
### From Source
-If you want to modify FLORAS or contribute, you can also install it directly from source (see [here](contributing.md)).
+If you want to modify FLORAS or contribute, you can install it directly from source (see [here](contributing.md)).
### Troubleshooting
Here are common errors we encountered and what we learned to fix the problem.
From 6a6ed7b9e8eed8773cd9b2dbf55f803e86d66faa Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:49:42 -0700
Subject: [PATCH 06/17] add graphviz
---
pdm.lock | 34 +++++++++++++++++-----------------
pyproject.toml | 5 ++++-
2 files changed, 21 insertions(+), 18 deletions(-)
diff --git a/pdm.lock b/pdm.lock
index 2d793f8..cf8d512 100644
--- a/pdm.lock
+++ b/pdm.lock
@@ -5,17 +5,17 @@
groups = ["default"]
strategy = ["inherit_metadata"]
lock_version = "4.5.0"
-content_hash = "sha256:fe4ac9b8495cac5eaff1b6fd13c077ca0dd971ee3d13273299be9709b82befc5"
+content_hash = "sha256:49837d73e6af3b303b328025645b7b986bf3c372683613e8274c69984b4695b2"
[[metadata.targets]]
-requires_python = ">=3.10"
+requires_python = "==3.10"
[[package]]
name = "asttokens"
version = "2.4.1"
summary = "Annotate AST trees with source code positions"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
dependencies = [
"six>=1.12.0",
"typing; python_version < \"3.5\"",
@@ -306,7 +306,7 @@ version = "5.1.1"
requires_python = ">=3.5"
summary = "Decorators for Humans"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
files = [
{file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"},
{file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"},
@@ -330,7 +330,7 @@ version = "2.1.0"
requires_python = ">=3.8"
summary = "Get the currently executing AST node of a frame, and other information"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
files = [
{file = "executing-2.1.0-py2.py3-none-any.whl", hash = "sha256:8d63781349375b5ebccc3142f4b30350c0cd9c79f921cde38be2be4637e98eaf"},
{file = "executing-2.1.0.tar.gz", hash = "sha256:8ea27ddd260da8150fa5a708269c4a10e76161e2496ec3e587da9e3c0fe4b9ab"},
@@ -517,7 +517,7 @@ version = "8.27.0"
requires_python = ">=3.10"
summary = "IPython: Productive Interactive Computing"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
dependencies = [
"colorama; sys_platform == \"win32\"",
"decorator",
@@ -542,7 +542,7 @@ version = "0.19.1"
requires_python = ">=3.6"
summary = "An autocompletion tool for Python that can be used for text editors."
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
dependencies = [
"parso<0.9.0,>=0.8.3",
]
@@ -756,7 +756,7 @@ version = "0.1.7"
requires_python = ">=3.8"
summary = "Inline Matplotlib backend for Jupyter"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
dependencies = [
"traitlets",
]
@@ -1039,7 +1039,7 @@ version = "0.8.4"
requires_python = ">=3.6"
summary = "A Python Parser"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
files = [
{file = "parso-0.8.4-py2.py3-none-any.whl", hash = "sha256:a418670a20291dacd2dddc80c377c5c3791378ee1e8d12bffc35420643d43f18"},
{file = "parso-0.8.4.tar.gz", hash = "sha256:eb3a7b58240fb99099a345571deecc0f9540ea5f4dd2fe14c2a99d6b281ab92d"},
@@ -1061,7 +1061,7 @@ name = "pexpect"
version = "4.9.0"
summary = "Pexpect allows easy control of interactive console applications."
groups = ["default"]
-marker = "(sys_platform != \"win32\" and sys_platform != \"emscripten\") and python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\" and (sys_platform != \"win32\" and sys_platform != \"emscripten\")"
dependencies = [
"ptyprocess>=0.5",
]
@@ -1183,7 +1183,7 @@ version = "3.0.48"
requires_python = ">=3.7.0"
summary = "Library for building powerful interactive command lines in Python"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
dependencies = [
"wcwidth",
]
@@ -1214,7 +1214,7 @@ name = "ptyprocess"
version = "0.7.0"
summary = "Run a subprocess in a pseudo terminal"
groups = ["default"]
-marker = "(sys_platform != \"win32\" and sys_platform != \"emscripten\") and python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\" and (sys_platform != \"win32\" and sys_platform != \"emscripten\")"
files = [
{file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"},
{file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"},
@@ -1225,7 +1225,7 @@ name = "pure-eval"
version = "0.2.3"
summary = "Safely evaluate AST nodes without side effects"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
files = [
{file = "pure_eval-0.2.3-py3-none-any.whl", hash = "sha256:1db8e35b67b3d218d818ae653e27f06c3aa420901fa7b081ca98cbedc874e0d0"},
{file = "pure_eval-0.2.3.tar.gz", hash = "sha256:5f4e983f40564c576c7c8635ae88db5956bb2229d7e9237d03b3c0b0190eaf42"},
@@ -1545,7 +1545,7 @@ name = "stack-data"
version = "0.6.3"
summary = "Extract data from python stack frames and tracebacks for informative displays"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
dependencies = [
"asttokens>=2.1.0",
"executing>=1.2.0",
@@ -1574,7 +1574,7 @@ version = "5.14.3"
requires_python = ">=3.8"
summary = "Traitlets Python configuration system"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
files = [
{file = "traitlets-5.14.3-py3-none-any.whl", hash = "sha256:b74e89e397b1ed28cc831db7aea759ba6640cb3de13090ca145426688ff1ac4f"},
{file = "traitlets-5.14.3.tar.gz", hash = "sha256:9ed0579d3502c94b4b3732ac120375cda96f923114522847de4b3bb98b96b6b7"},
@@ -1605,7 +1605,7 @@ version = "4.12.2"
requires_python = ">=3.8"
summary = "Backported and Experimental Type Hints for Python 3.8+"
groups = ["default"]
-marker = "python_version < \"3.12\" and python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
files = [
{file = "typing_extensions-4.12.2-py3-none-any.whl", hash = "sha256:04e5ca0351e0f3f85c6853954072df659d0d13fac324d0072316b67d7794700d"},
{file = "typing_extensions-4.12.2.tar.gz", hash = "sha256:1a7ead55c7e559dd4dee8856e3a88b41225abfe1ce8df57b7c13915fe121ffb8"},
@@ -1661,7 +1661,7 @@ name = "wcwidth"
version = "0.2.13"
summary = "Measures the displayed width of unicode strings in a terminal"
groups = ["default"]
-marker = "python_version > \"3.6\""
+marker = "python_version > \"3.6\" and python_version < \"3.11\""
dependencies = [
"backports-functools-lru-cache>=1.2.1; python_version < \"3.2\"",
]
diff --git a/pyproject.toml b/pyproject.toml
index 3b3073c..3e707e5 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,10 +30,13 @@ dependencies = [
"pytest>=8.3.3",
"coverage>=7.6.1",
"pygraphviz>=1.13",
+ "graphviz>=0.20.3",
]
-requires-python = ">=3.10"
+requires-python = "==3.10"
version = "0.0.0"
readme = "README.md"
[project.scripts]
from_json = "flowsynth.main:main"
+
+[tool.pdm]
From 7e0cdd9068869d78c3732fcc3b92ec8b24edbee3 Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:54:05 -0700
Subject: [PATCH 07/17] update installation instructions
---
docs/installing.md | 7 +------
1 file changed, 1 insertion(+), 6 deletions(-)
diff --git a/docs/installing.md b/docs/installing.md
index 8ac9ec9..f6be9b6 100644
--- a/docs/installing.md
+++ b/docs/installing.md
@@ -1,13 +1,8 @@
# Installing FLORAS
### Requirements
-FLORAS requires `Python>=3.10` and a C++17-compliant compiler (for example `g++>=7.0` or `clang++>=5.0`).
+FLORAS requires `Python==3.10` and a C++17-compliant compiler (for example `g++>=7.0` or `clang++>=5.0`).
You can check the versions by running `python --version` and `gcc --version`.
-
### From Source
If you want to modify FLORAS or contribute, you can install it directly from source (see [here](contributing.md)).
From 08dd521e42d746f0897532df944b4dc5a85562d2 Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 14:58:15 -0700
Subject: [PATCH 08/17] update index
---
docs/index.md | 2 ++
1 file changed, 2 insertions(+)
diff --git a/docs/index.md b/docs/index.md
index 9ed5698..ba2c319 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -2,5 +2,7 @@
FLORAS is an open-source Python package for reactive test synthesis for autonomous systems using network flows.
+Installation instructions can be found [here](contributing.md).
+
If you use FLORAS for research purposes, please acknowledge it by citing
[Josefine B. Graebener\*, Apurva S. Badithela\*, Denizalp Goktas, Wyatt Ubellacker, Eric V. Mazumdar, Aaron D. Ames, and Richard M. Murray. "Flow-Based Synthesis of Reactive Tests for Discrete Decision-Making Systems with Temporal Logic Specifications." ArXiv abs/2404.09888 (2024).](https://arxiv.org/abs/2404.09888)
From 4829c682750136ff55fc3006e9c7315ae94f01bf Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 19:48:25 -0700
Subject: [PATCH 09/17] rename
---
.../components/automata.py | 2 +-
.../components/product.py | 4 +-
.../components/transition_system.py | 0
src/{flowsynth => floras}/components/utils.py | 0
src/{flowsynth => floras}/main.py | 10 +-
.../optimization/optimization.py | 2 +-
.../optimization/optimize.py | 4 +-
.../optimization/utils.py | 0
src/flowsynth/optimization/milp_static.py | 229 ------------------
src/flowsynth/optimization/setup_graphs.py | 78 ------
tests/test_1.py | 63 -----
tests/test_reactive_1.py | 8 +-
tests/test_static_1.py | 36 +++
13 files changed, 51 insertions(+), 385 deletions(-)
rename src/{flowsynth => floras}/components/automata.py (99%)
rename src/{flowsynth => floras}/components/product.py (98%)
rename src/{flowsynth => floras}/components/transition_system.py (100%)
rename src/{flowsynth => floras}/components/utils.py (100%)
rename src/{flowsynth => floras}/main.py (87%)
rename src/{flowsynth => floras}/optimization/optimization.py (99%)
rename src/{flowsynth => floras}/optimization/optimize.py (72%)
rename src/{flowsynth => floras}/optimization/utils.py (100%)
delete mode 100644 src/flowsynth/optimization/milp_static.py
delete mode 100644 src/flowsynth/optimization/setup_graphs.py
delete mode 100644 tests/test_1.py
create mode 100644 tests/test_static_1.py
diff --git a/src/flowsynth/components/automata.py b/src/floras/components/automata.py
similarity index 99%
rename from src/flowsynth/components/automata.py
rename to src/floras/components/automata.py
index e9203ea..672ecf2 100644
--- a/src/flowsynth/components/automata.py
+++ b/src/floras/components/automata.py
@@ -8,7 +8,7 @@
from collections import OrderedDict as od
import re
import os
-from flowsynth.components.utils import powerset, neg, conjunction, disjunction
+from floras.components.utils import powerset, neg, conjunction, disjunction
class Automaton:
diff --git a/src/flowsynth/components/product.py b/src/floras/components/product.py
similarity index 98%
rename from src/flowsynth/components/product.py
rename to src/floras/components/product.py
index 7f7ca34..5e31b8a 100644
--- a/src/flowsynth/components/product.py
+++ b/src/floras/components/product.py
@@ -8,8 +8,8 @@
import os
import networkx as nx
from itertools import product
-from flowsynth.components.transition_system import TranSys
-from flowsynth.components.automata import Automaton
+from floras.components.transition_system import TranSys
+from floras.components.automata import Automaton
spot.setup(show_default='.tvb')
diff --git a/src/flowsynth/components/transition_system.py b/src/floras/components/transition_system.py
similarity index 100%
rename from src/flowsynth/components/transition_system.py
rename to src/floras/components/transition_system.py
diff --git a/src/flowsynth/components/utils.py b/src/floras/components/utils.py
similarity index 100%
rename from src/flowsynth/components/utils.py
rename to src/floras/components/utils.py
diff --git a/src/flowsynth/main.py b/src/floras/main.py
similarity index 87%
rename from src/flowsynth/main.py
rename to src/floras/main.py
index 5e61a62..04946f8 100644
--- a/src/flowsynth/main.py
+++ b/src/floras/main.py
@@ -2,11 +2,11 @@
import ast
import argparse
-from flowsynth.optimization.optimize import solve
-from flowsynth.components.automata import get_system_automaton, get_tester_automaton, get_product_automaton
-from flowsynth.components.transition_system import TranSys, TransitionSystemInput
-from flowsynth.components.product import sync_prod
-from flowsynth.components.utils import get_states_and_transitions_from_file
+from floras.optimization.optimize import solve
+from floras.components.automata import get_system_automaton, get_tester_automaton, get_product_automaton
+from floras.components.transition_system import TranSys, TransitionSystemInput
+from floras.components.product import sync_prod
+from floras.components.utils import get_states_and_transitions_from_file
from ipdb import set_trace as st
diff --git a/src/flowsynth/optimization/optimization.py b/src/floras/optimization/optimization.py
similarity index 99%
rename from src/flowsynth/optimization/optimization.py
rename to src/floras/optimization/optimization.py
index a431151..889424f 100644
--- a/src/flowsynth/optimization/optimization.py
+++ b/src/floras/optimization/optimization.py
@@ -5,7 +5,7 @@
import time
import numpy as np
import networkx as nx
-from flowsynth.optimization.utils import find_map_G_S
+from floras.optimization.utils import find_map_G_S
from gurobipy import *
from copy import deepcopy
import os
diff --git a/src/flowsynth/optimization/optimize.py b/src/floras/optimization/optimize.py
similarity index 72%
rename from src/flowsynth/optimization/optimize.py
rename to src/floras/optimization/optimize.py
index 843ed27..7478b16 100644
--- a/src/flowsynth/optimization/optimize.py
+++ b/src/floras/optimization/optimize.py
@@ -1,5 +1,5 @@
-from flowsynth.optimization.setup_graphs import setup_nodes_and_edges
-from flowsynth.optimization.optimization import MILP
+from floras.optimization.setup_graphs import setup_nodes_and_edges
+from floras.optimization.optimization import MILP
from ipdb import set_trace as st
diff --git a/src/flowsynth/optimization/utils.py b/src/floras/optimization/utils.py
similarity index 100%
rename from src/flowsynth/optimization/utils.py
rename to src/floras/optimization/utils.py
diff --git a/src/flowsynth/optimization/milp_static.py b/src/flowsynth/optimization/milp_static.py
deleted file mode 100644
index 2924395..0000000
--- a/src/flowsynth/optimization/milp_static.py
+++ /dev/null
@@ -1,229 +0,0 @@
-'''
-Gurobipy implementation of the MILP for static obstacles.
-'''
-from gurobipy import GRB
-import time
-import numpy as np
-from ipdb import set_trace as st
-import networkx as nx
-from src.flowsynth.optimization.utils import find_map_G_S
-from gurobipy import *
-from copy import deepcopy
-import os
-import json
-
-# Callback function
-def cb(model, where):
- if where == GRB.Callback.MIPNODE:
- # Get model objective
- obj = model.cbGet(GRB.Callback.MIPNODE_OBJBST) # Current best objective
- sol_count = model.cbGet(GRB.Callback.MIPNODE_SOLCNT) # No. of feasible solns found.
-
- # Has objective changed?
- if abs(obj - model._cur_obj) > 1e-8:
- # If so, update incumbent
- model._cur_obj = obj
-
- # Terminate if objective has not improved in 60s
- # Current objective is less than infinity.
-
- if sol_count >= 1:
- if time.time() - model._time > 60:
- model._data["term_condition"] = "Obj not changing"
- model.terminate()
- else:
- # Total termination time if the optimizer has not found anything in 5 min:
- if time.time() - model._time > 600:
- model._data["term_condition"] = "Timeout"
- model.terminate()
-
-# Gurobi implementation
-def solve_opt_static(GD, SD, callback="cb", logger=None, logger_runtime_dict=None):
- cleaned_intermed = [x for x in GD.acc_test if x not in GD.acc_sys]
- # create G and remove self-loops
- G = GD.graph
- to_remove = []
- for i, j in G.edges:
- if i == j:
- to_remove.append((i,j))
- G.remove_edges_from(to_remove)
-
- # remove intermediate nodes
- G_minus_I = deepcopy(G)
- G_minus_I.remove_nodes_from(cleaned_intermed)
-
- # create S and remove self-loops
- S = SD.graph
- to_remove = []
- for i, j in S.edges:
- if i == j:
- to_remove.append((i,j))
- S.remove_edges_from(to_remove)
-
- model_edges = list(G.edges)
- model_nodes = list(G.nodes)
- model_edges_without_I = list(G_minus_I.edges)
- model_nodes_without_I = list(G_minus_I.nodes)
-
- src = GD.init
- sink = GD.sink
- inter = cleaned_intermed
-
- # for the flow on S
- map_G_to_S = find_map_G_S(GD,SD)
-
- s_sink = SD.acc_sys
- s_src = SD.init[0]
-
-
- model_s_edges = list(S.edges)
- model_s_nodes = list(S.nodes)
-
- model = Model()
- # Define variables
- f = model.addVars(model_edges, name="flow")
- m = model.addVars(model_nodes_without_I, name="m")
- d = model.addVars(model_edges, vtype=GRB.BINARY, name="d")
-
- # Define Objective
- term = sum(f[i,j] for (i, j) in model_edges if i in src)
- ncuts = sum(d[i,j] for (i, j) in model_edges)
- reg = 1/len(model_edges)
- model.setObjective(term - reg*ncuts, GRB.MAXIMIZE)
-
- # Define constraints
- # Nonnegativity - lower bounds
- model.addConstrs((d[i, j] >= 0 for (i,j) in model_edges), name='d_nonneg')
- model.addConstrs((m[i] >= 0 for i in model_nodes_without_I), name='mu_nonneg')
- model.addConstrs((f[i, j] >= 0 for (i,j) in model_edges), name='f_nonneg')
-
- # upper bounds
- model.addConstrs((d[i, j] <= 1 for (i,j) in model_edges), name='d_upper_b')
- model.addConstrs((m[i] <= 1 for i in model_nodes_without_I), name='mu_upper_b')
- # capacity (upper bound for f)
- model.addConstrs((f[i, j] <= 1 for (i,j) in model_edges), name='capacity')
-
- # preserve flow of at least 1
- model.addConstr((1 <= sum(f[i,j] for (i, j) in model_edges if i in src)), name='conserve_F')
-
- # conservation
- model.addConstrs((sum(f[i,j] for (i,j) in model_edges if j == l) == sum(f[i,j] for (i,j) in model_edges if i == l) for l in model_nodes if l not in src and l not in sink), name='conservation')
-
- # no flow into source or out of sink
- model.addConstrs((f[i,j] == 0 for (i,j) in model_edges if j in src or i in sink), name="no_out_sink_in_src")
-
- # cut constraint (cut edges have zero flow)
- model.addConstrs((f[i,j] + d[i,j] <= 1 for (i,j) in model_edges), name='cut_cons')
-
- # source sink partitions
- for i in model_nodes_without_I:
- for j in model_nodes_without_I:
- if i in src and j in sink:
- model.addConstr(m[i] - m[j] >= 1)
-
- # max flow cut constraint (cut variable d partitions the groups)
- model.addConstrs((d[i,j] - m[i] + m[j] >= 0 for (i,j) in model_edges_without_I))
-
- # --------- map static obstacles to other edges in G
- for count, (i,j) in enumerate(model_edges):
- out_state = GD.node_dict[i][0]
- in_state = GD.node_dict[j][0]
- for (imap,jmap) in model_edges[count+1:]:
- if out_state == GD.node_dict[imap][0] and in_state == GD.node_dict[jmap][0]:
- model.addConstr(d[i, j] == d[imap, jmap])
-
-
- # --------- add bidirectional cuts on G
- for count, (i,j) in enumerate(model_edges):
- out_state = GD.node_dict[i][0]
- in_state = GD.node_dict[j][0]
- for (imap,jmap) in model_edges[count+1:]:
- if in_state == GD.node_dict[imap][0] and out_state == GD.node_dict[jmap][0]:
- model.addConstr(d[i, j] == d[imap, jmap])
-
- # --------- set parameters
- # Last updated objective and time (for callback function)
- model._cur_obj = float('inf')
- model._time = time.time()
- model.Params.Seed = np.random.randint(0,100)
-
- # store model data for logging
- model._data = dict()
- model._data["term_condition"] = None
-
- # optimize
- if callback=="cb":
- t0 = time.time()
- model.optimize(callback=cb)
- tf = time.time()
- delt = tf - t0
- else:
- t0 = time.time()
- model.optimize()
- tf = time.time()
- delt = tf - t0
-
- model._data["runtime"] = model.Runtime
- model._data["flow"] = None
- model._data["ncuts"] = None
-
- # Storing problem variables:
- model._data["n_bin_vars"] = model.NumBinVars
- model._data["n_cont_vars"] = model.NumVars - model.NumBinVars
- model._data["n_constrs"] = model.NumConstrs
-
- f_vals = []
- d_vals = []
- flow = None
- exit_status = None
-
- if model.status == 4:
- model.Params.DualReductions = 0
- exit_status = 'inf'
- model._data["status"] = "inf/unbounded"
- return 0,0,exit_status
- elif model.status == 11 and model.SolCount < 1:
- exit_status = 'not solved'
- model._data["status"] = "not_solved"
- model._data["exit_status"] = exit_status
- elif model.status == 2 or (model.status == 11 and model.SolCount >= 1):
- if model.status == 2:
- model._data["status"] = "optimal"
- model._data["term_condition"] = "optimal found"
- else:
- # feasible. maybe be optimal.
- model._data["status"] = "feasible"
-
- # --------- parse output
- d_vals = dict()
- f_vals = dict()
-
- for (i,j) in model_edges:
- f_vals.update({(i,j): f[i,j].X})
- for (i,j) in model_edges:
- d_vals.update({(i,j): d[i,j].X})
-
- flow = sum(f[i,j].X for (i,j) in model_edges if i in src)
- model._data["flow"] = flow
- ncuts = 0
-
- for key in d_vals.keys():
- if d_vals[key] > 0.9:
- ncuts+=1
- print('{0} to {1} at {2}'.format(GD.node_dict[key[0]], GD.node_dict[key[1]],d_vals[key]))
-
- model._data["ncuts"] = ncuts
- exit_status = 'opt'
- model._data["exit_status"] = exit_status
- elif model.status == 3:
- exit_status = 'inf'
- model._data["status"] = "inf"
- else:
- st()
-
- if not os.path.exists("log"):
- os.makedirs("log")
- with open('log/opt_data.json', 'w') as fp:
- json.dump(model._data, fp)
-
- return d_vals, flow, exit_status
diff --git a/src/flowsynth/optimization/setup_graphs.py b/src/flowsynth/optimization/setup_graphs.py
deleted file mode 100644
index e9bec4e..0000000
--- a/src/flowsynth/optimization/setup_graphs.py
+++ /dev/null
@@ -1,78 +0,0 @@
-"""Contains GraphData class for optimization and parses the virtual graphs into the required form."""
-import networkx as nx
-
-class GraphData:
- def __init__(self, nodes, edges, node_dict, inv_node_dict, acc_sys, acc_test, init):
- self.nodes = nodes
- self.edges = edges
- self.node_dict = node_dict
- self.inv_node_dict = inv_node_dict
- self.acc_sys = acc_sys
- self.acc_test = acc_test
- self.init = init
- self.graph = self.setup_graph(nodes, edges)
- self.int = self.acc_test
- self.sink = self.acc_sys
-
- def setup_graph(self, nodes, edges):
- G = nx.DiGraph()
- G.add_nodes_from(nodes)
- G.add_edges_from(edges)
- return G
-
-
-def setup_nodes_and_edges(virtual_game_graph, virtual_sys, b_pi):
- # setup nodes and map
- nodes = []
- node_dict = {}
- inv_node_dict = {}
- for i, node in enumerate(virtual_game_graph.G_initial.nodes):
- nodes.append(i)
- node_dict.update({i: virtual_game_graph.reverse_Sdict[node]})
- inv_node_dict.update({virtual_game_graph.reverse_Sdict[node]: i})
- # find initial state
- init = []
- for initial in virtual_game_graph.I:
- init.append(inv_node_dict[initial])
- # find accepting states for system and tester
- acc_sys = []
- acc_test = []
- for node in nodes:
- if node_dict[node] in virtual_game_graph.sink:
- acc_sys.append(node)
- if node_dict[node] in virtual_game_graph.int:
- acc_test.append(node)
- # setup edges
- edges = []
- for edge in virtual_game_graph.G_initial.edges:
- out_node = virtual_game_graph.reverse_Sdict[edge[0]]
- in_node = virtual_game_graph.reverse_Sdict[edge[1]]
- edges.append((inv_node_dict[out_node],inv_node_dict[in_node]))
-
- # setup system graph
- S_nodes = []
- S_node_dict = {}
- S_inv_node_dict = {}
- for i, node in enumerate(virtual_sys.G_initial.nodes):
- S_nodes.append(i)
- S_node_dict.update({i: virtual_sys.reverse_Sdict[node]})
- S_inv_node_dict.update({virtual_sys.reverse_Sdict[node]: i})
- # find initial state
- S_init = []
- for initial in virtual_sys.I:
- S_init.append(S_inv_node_dict[initial])
- # find accepting states for system
- S_acc_sys = []
- for node in S_nodes:
- if S_node_dict[node] in virtual_sys.sink:
- S_acc_sys.append(node)
- # setup edges
- S_edges = []
- for edge in virtual_sys.G_initial.edges:
- out_node = virtual_sys.reverse_Sdict[edge[0]]
- in_node = virtual_sys.reverse_Sdict[edge[1]]
- S_edges.append((S_inv_node_dict[out_node],S_inv_node_dict[in_node]))
-
- GD = GraphData(nodes, edges, node_dict, inv_node_dict, acc_sys, acc_test, init)
- S = GraphData(S_nodes, S_edges, S_node_dict, S_inv_node_dict, S_acc_sys, [], S_init)
- return GD, S
diff --git a/tests/test_1.py b/tests/test_1.py
deleted file mode 100644
index a82a438..0000000
--- a/tests/test_1.py
+++ /dev/null
@@ -1,63 +0,0 @@
-"""Testing current code."""
-import pytest
-
-import sys
-sys.path.append('../')
-from flowsynth.components.automata import get_system_automaton, get_tester_automaton, get_product_automaton
-from flowsynth.components.transition_system import TransitionSystemInput, TranSys
-from flowsynth.components.product import sync_prod
-from flowsynth.optimization.optimize import solve
-
-def test_sample():
- states_list = [0,1,2,3,4,5]
- transitions_dict = {0: [1,2,3], 1: [2,3,4], 2: [3,4,5], 3: [4], 4: [5,0], 5: [5]}
- labels_dict = {0 : ['a'], 5: ['goal'], 3: ['int']}
- init_list = [0]
-
- transition_system_input = TransitionSystemInput(states_list, transitions_dict, labels_dict, init_list)
-
- sys_formula = 'F(goal)'
- test_formula = 'F(int)'
-
- # get automata
- sys_aut, spot_aut_sys = get_system_automaton(sys_formula)
- test_aut, spot_aut_test = get_tester_automaton(test_formula)
- prod_aut = get_product_automaton(spot_aut_sys, spot_aut_test)
-
- # get transition system
- transys = TranSys(transition_system_input)
-
- # get virtual graphs
- virtual_sys = sync_prod(transys, sys_aut)
- virtual = sync_prod(transys, prod_aut)
-
- d, flow = solve(virtual, transys, prod_aut, virtual_sys, case = 'static')
-
- assert flow >= 1.0
-
-if __name__=='__main__':
- states_list = [0,1,2,3,4,5]
- transitions_dict = {0: [1,2,3], 1: [2,3,4], 2: [3,4,5], 3: [4], 4: [5,0], 5: [5]}
- labels_dict = {0 : ['a'], 5: ['goal'], 3: ['int']}
- init_list = [0]
-
- transition_system_input = TransitionSystemInput(states_list, transitions_dict, labels_dict, init_list)
-
- sys_formula = 'F(goal)'
- test_formula = 'F(int)'
-
- # get automata
- sys_aut, spot_aut_sys = get_system_automaton(sys_formula)
- test_aut, spot_aut_test = get_tester_automaton(test_formula)
- prod_aut = get_product_automaton(spot_aut_sys, spot_aut_test)
-
- # get transition system
- transys = TranSys(transition_system_input)
-
- # get virtual graphs
- virtual_sys = sync_prod(transys, sys_aut)
- virtual = sync_prod(transys, prod_aut)
-
- d, flow = solve(virtual, transys, prod_aut, virtual_sys, case = 'static')
-
- assert flow >= 1.0
diff --git a/tests/test_reactive_1.py b/tests/test_reactive_1.py
index 1be1f68..3cc2640 100644
--- a/tests/test_reactive_1.py
+++ b/tests/test_reactive_1.py
@@ -3,10 +3,10 @@
import sys
sys.path.append('../')
-from flowsynth.components.automata import get_system_automaton, get_tester_automaton, get_product_automaton
-from flowsynth.components.transition_system import TransitionSystemInput, TranSys
-from flowsynth.components.product import sync_prod
-from flowsynth.optimization.optimize import solve
+from floras.components.automata import get_system_automaton, get_tester_automaton, get_product_automaton
+from floras.components.transition_system import TransitionSystemInput, TranSys
+from floras.components.product import sync_prod
+from floras.optimization.optimize import solve
def test_reactive():
states_list = ['init', 'd1', 'd2', 'int_goal', 'p1', 'p2', 'goal']
diff --git a/tests/test_static_1.py b/tests/test_static_1.py
new file mode 100644
index 0000000..90af25c
--- /dev/null
+++ b/tests/test_static_1.py
@@ -0,0 +1,36 @@
+"""Testing current code in static setup."""
+import pytest
+
+import sys
+sys.path.append('../')
+from floras.components.automata import get_system_automaton, get_tester_automaton, get_product_automaton
+from floras.components.transition_system import TransitionSystemInput, TranSys
+from floras.components.product import sync_prod
+from floras.optimization.optimize import solve
+
+def test_static():
+ states_list = [0,1,2,3,4,5]
+ transitions_dict = {0: [1,2,3], 1: [2,3,4], 2: [3,4,5], 3: [4], 4: [5,0], 5: [5]}
+ labels_dict = {0 : ['a'], 5: ['goal'], 3: ['int']}
+ init_list = [0]
+
+ transition_system_input = TransitionSystemInput(states_list, transitions_dict, labels_dict, init_list)
+
+ sys_formula = 'F(goal)'
+ test_formula = 'F(int)'
+
+ # get automata
+ sys_aut, spot_aut_sys = get_system_automaton(sys_formula)
+ test_aut, spot_aut_test = get_tester_automaton(test_formula)
+ prod_aut = get_product_automaton(spot_aut_sys, spot_aut_test)
+
+ # get transition system
+ transys = TranSys(transition_system_input)
+
+ # get virtual graphs
+ virtual_sys = sync_prod(transys, sys_aut)
+ virtual = sync_prod(transys, prod_aut)
+
+ d, flow = solve(virtual, transys, prod_aut, virtual_sys, case = 'static')
+
+ assert flow >= 1.0
From 3570f9ef042c930101402e5784326e024ea1c23d Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 19:49:00 -0700
Subject: [PATCH 10/17] add missing file
---
src/floras/optimization/setup_graphs.py | 78 +++++++++++++++++++++++++
1 file changed, 78 insertions(+)
create mode 100644 src/floras/optimization/setup_graphs.py
diff --git a/src/floras/optimization/setup_graphs.py b/src/floras/optimization/setup_graphs.py
new file mode 100644
index 0000000..e9bec4e
--- /dev/null
+++ b/src/floras/optimization/setup_graphs.py
@@ -0,0 +1,78 @@
+"""Contains GraphData class for optimization and parses the virtual graphs into the required form."""
+import networkx as nx
+
+class GraphData:
+ def __init__(self, nodes, edges, node_dict, inv_node_dict, acc_sys, acc_test, init):
+ self.nodes = nodes
+ self.edges = edges
+ self.node_dict = node_dict
+ self.inv_node_dict = inv_node_dict
+ self.acc_sys = acc_sys
+ self.acc_test = acc_test
+ self.init = init
+ self.graph = self.setup_graph(nodes, edges)
+ self.int = self.acc_test
+ self.sink = self.acc_sys
+
+ def setup_graph(self, nodes, edges):
+ G = nx.DiGraph()
+ G.add_nodes_from(nodes)
+ G.add_edges_from(edges)
+ return G
+
+
+def setup_nodes_and_edges(virtual_game_graph, virtual_sys, b_pi):
+ # setup nodes and map
+ nodes = []
+ node_dict = {}
+ inv_node_dict = {}
+ for i, node in enumerate(virtual_game_graph.G_initial.nodes):
+ nodes.append(i)
+ node_dict.update({i: virtual_game_graph.reverse_Sdict[node]})
+ inv_node_dict.update({virtual_game_graph.reverse_Sdict[node]: i})
+ # find initial state
+ init = []
+ for initial in virtual_game_graph.I:
+ init.append(inv_node_dict[initial])
+ # find accepting states for system and tester
+ acc_sys = []
+ acc_test = []
+ for node in nodes:
+ if node_dict[node] in virtual_game_graph.sink:
+ acc_sys.append(node)
+ if node_dict[node] in virtual_game_graph.int:
+ acc_test.append(node)
+ # setup edges
+ edges = []
+ for edge in virtual_game_graph.G_initial.edges:
+ out_node = virtual_game_graph.reverse_Sdict[edge[0]]
+ in_node = virtual_game_graph.reverse_Sdict[edge[1]]
+ edges.append((inv_node_dict[out_node],inv_node_dict[in_node]))
+
+ # setup system graph
+ S_nodes = []
+ S_node_dict = {}
+ S_inv_node_dict = {}
+ for i, node in enumerate(virtual_sys.G_initial.nodes):
+ S_nodes.append(i)
+ S_node_dict.update({i: virtual_sys.reverse_Sdict[node]})
+ S_inv_node_dict.update({virtual_sys.reverse_Sdict[node]: i})
+ # find initial state
+ S_init = []
+ for initial in virtual_sys.I:
+ S_init.append(S_inv_node_dict[initial])
+ # find accepting states for system
+ S_acc_sys = []
+ for node in S_nodes:
+ if S_node_dict[node] in virtual_sys.sink:
+ S_acc_sys.append(node)
+ # setup edges
+ S_edges = []
+ for edge in virtual_sys.G_initial.edges:
+ out_node = virtual_sys.reverse_Sdict[edge[0]]
+ in_node = virtual_sys.reverse_Sdict[edge[1]]
+ S_edges.append((S_inv_node_dict[out_node],S_inv_node_dict[in_node]))
+
+ GD = GraphData(nodes, edges, node_dict, inv_node_dict, acc_sys, acc_test, init)
+ S = GraphData(S_nodes, S_edges, S_node_dict, S_inv_node_dict, S_acc_sys, [], S_init)
+ return GD, S
From 67c07600c7926bfd7d9d402456b6213a8074caf4 Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 20:01:05 -0700
Subject: [PATCH 11/17] update docs
---
README.md | 8 +++++---
docs/automata.md | 2 +-
docs/index.md | 5 +++++
docs/optimization.md | 2 +-
docs/product.md | 2 +-
docs/transition_system.md | 2 +-
mkdocs.yml | 3 +++
pyproject.toml | 6 +++---
8 files changed, 20 insertions(+), 10 deletions(-)
diff --git a/README.md b/README.md
index 3040ad8..eee1473 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,7 @@
-# flowsynth
+# Floras: Flow-Based Reactive Test Synthesis for Autonomous Systems
-The flowsynth repository contains implementations of the algorithms developed in the following paper:
+![logo](docs/logo.png "floras logo")
-[Josefine B. Graebener*, Apurva S. Badithela*, Denizalp Goktas, Wyatt Ubellacker, Eric V. Mazumdar, Aaron D. Ames, and Richard M. Murray. "Flow-Based Synthesis of Reactive Tests for Discrete Decision-Making Systems with Temporal Logic Specifications." ArXiv abs/2404.09888 (2024).](https://arxiv.org/abs/2404.09888)
\ No newline at end of file
+The floras repository contains implementations of the algorithms developed in the following paper:
+
+[Josefine B. Graebener*, Apurva S. Badithela*, Denizalp Goktas, Wyatt Ubellacker, Eric V. Mazumdar, Aaron D. Ames, and Richard M. Murray. "Flow-Based Synthesis of Reactive Tests for Discrete Decision-Making Systems with Temporal Logic Specifications." ArXiv abs/2404.09888 (2024).](https://arxiv.org/abs/2404.09888)
diff --git a/docs/automata.md b/docs/automata.md
index 399c26b..7dd5c79 100644
--- a/docs/automata.md
+++ b/docs/automata.md
@@ -1 +1 @@
-::: flowsynth.components.automata
+::: floras.components.automata
diff --git a/docs/index.md b/docs/index.md
index ba2c319..11feccd 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -1,5 +1,10 @@
# FLORAS - Flow-based Reactive Test Synthesis for Autonomous Systems
+
+
FLORAS is an open-source Python package for reactive test synthesis for autonomous systems using network flows.
Installation instructions can be found [here](contributing.md).
diff --git a/docs/optimization.md b/docs/optimization.md
index a9b812e..46b741b 100644
--- a/docs/optimization.md
+++ b/docs/optimization.md
@@ -1 +1 @@
-::: flowsynth.optimization.optimization
+::: floras.optimization.optimization
diff --git a/docs/product.md b/docs/product.md
index 58352e4..d08d333 100644
--- a/docs/product.md
+++ b/docs/product.md
@@ -1 +1 @@
-::: flowsynth.components.product
+::: floras.components.product
diff --git a/docs/transition_system.md b/docs/transition_system.md
index 11f9450..0a6b266 100644
--- a/docs/transition_system.md
+++ b/docs/transition_system.md
@@ -1 +1 @@
-::: flowsynth.components.transition_system
+::: floras.components.transition_system
diff --git a/mkdocs.yml b/mkdocs.yml
index 9d30351..cb35491 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -23,6 +23,9 @@ plugins:
python:
paths: [src] # search packages in the src folder
+markdown_extensions:
+ - attr_list
+ - md_in_html
copyright: >
Copyright © 2024 - Caltech
diff --git a/pyproject.toml b/pyproject.toml
index 3e707e5..f1c43c0 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -3,10 +3,10 @@ requires = ["pdm-backend"]
build-backend = "pdm.backend"
[project]
-name = "flowsynth"
+name = "floras"
description = "A package for network flow-based test synthesis."
authors = [
- {name = "flowsynth developers", email = "tbd"},
+ {name = "floras developers", email = "tbd"},
]
license = {text = "BSD-3-Clause"}
classifiers = [
@@ -37,6 +37,6 @@ version = "0.0.0"
readme = "README.md"
[project.scripts]
-from_json = "flowsynth.main:main"
+from_json = "floras.main:main"
[tool.pdm]
From ff0bd6371a0c7497f8ffbcca0006cae75ce7dec0 Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 20:03:20 -0700
Subject: [PATCH 12/17] add license
---
LICENSE.txt | 28 ++++++++++++++++++++++++++++
1 file changed, 28 insertions(+)
create mode 100644 LICENSE.txt
diff --git a/LICENSE.txt b/LICENSE.txt
new file mode 100644
index 0000000..3d6a5e4
--- /dev/null
+++ b/LICENSE.txt
@@ -0,0 +1,28 @@
+BSD 3-Clause License
+
+Copyright (c) 2024, California Institute of Technology
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+1. Redistributions of source code must retain the above copyright notice, this
+ list of conditions and the following disclaimer.
+
+2. Redistributions in binary form must reproduce the above copyright notice,
+ this list of conditions and the following disclaimer in the documentation
+ and/or other materials provided with the distribution.
+
+3. Neither the name of the copyright holder nor the names of its
+ contributors may be used to endorse or promote products derived from
+ this software without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
From 395df0863a6d4b2c491007ffe54346be1a5faa7d Mon Sep 17 00:00:00 2001
From: Josefine G <51681591+jgraeb@users.noreply.github.com>
Date: Tue, 1 Oct 2024 20:05:17 -0700
Subject: [PATCH 13/17] add logo
---
docs/logo.png | Bin 0 -> 159293 bytes
1 file changed, 0 insertions(+), 0 deletions(-)
create mode 100644 docs/logo.png
diff --git a/docs/logo.png b/docs/logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..1e9a6acec3bce8d042b93d9b008c38673a8858a8
GIT binary patch
literal 159293
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