From 589f0c41c28a67edaec33f55d83a1cd7248655c1 Mon Sep 17 00:00:00 2001 From: TobyBoyne Date: Thu, 3 Apr 2025 17:52:40 +0100 Subject: [PATCH 1/2] Draft example of a selector strategy in the documentation --- docs/javascripts/selectortabs.js | 92 ++++++++++++++++++++++++++++++++ docs/userguide_strategies.md | 19 +++++++ mkdocs.yaml | 9 ++++ 3 files changed, 120 insertions(+) create mode 100644 docs/javascripts/selectortabs.js create mode 100644 docs/userguide_strategies.md diff --git a/docs/javascripts/selectortabs.js b/docs/javascripts/selectortabs.js new file mode 100644 index 000000000..ee175f5ab --- /dev/null +++ b/docs/javascripts/selectortabs.js @@ -0,0 +1,92 @@ +const selectorTabStrategies = { + // map from tab labels to group names + "single-objective": "objective", + "multi-objective": "objective", + "single-task": "tasks", + "multi-task": "tasks", + "multi-fidelity": "tasks", + "simple-domain": "domain", + "many-categorical-features": "domain", +} + +let strategies = { + "objective": "single-objective", + "tasks": "single-task", + "domain": "simple-domain", +} + +const getStrategyCode = () => { + const objective = strategies["objective"]; + const tasks = strategies["tasks"]; + const domain = strategies["domain"]; + + let strategyDataModel = "" + if (objective == "single-objective") { + if (tasks == "single-task") { + if (domain == "simple-domain") { + strategyDataModel = "SoboStrategy" + } else if (domain == "many-categorical-features") { + strategyDataModel = "EntingStrategy" + } + } else if (tasks == "multi-task") { + strategyDataModel = "SoboStrategy" + } else if (tasks == "multi-fidelity") { + strategyDataModel = "MultiFidelityStrategy" + // we define multi fidelity as a multi-task where you can query the other fidelities + } + } else if (objective == "multi-objective") { + if (tasks == "single-task" && domain == "simple-domain") { + strategyDataModel = "MoboStrategy" + } + } + + return hljs.highlight( +`# import the data model +from bofire.data_models.strategies.api import ${strategyDataModel} +import bofire.strategies.api as strategies + +surrogate_data_model = BotorchSurrogates(surrogates=[ + MultiTaskGPSurrogate( + inputs=domain.inputs, + outputs=domain.outputs, + ) +]) + +strategy_data_model = ${strategyDataModel}( + domain=domain + surrogate=surrogate_data_model +) +strategy = strategies.map(strategy_data_model) +`, { language : "python"} + ) +} + +const updateStrategyCodeBlock = () => { + const codeBlock = document.getElementById("stategyTemplate") + codeBlock.innerHTML = getStrategyCode().value +} + +const tabSync = () => { + const tabs = document.querySelectorAll(".tabbed-set > input") + for (const tab of tabs) { + tab.addEventListener("click", () => { + const current = document.querySelector(`label[for=${tab.id}]`) + console.log(tab.id) + const pos = current.getBoundingClientRect().top + // const labels = document.querySelectorAll('.tabbed-set > label, .tabbed-alternate > .tabbed-labels > label') + + const updatedGroup = selectorTabStrategies[tab.id] + strategies[updatedGroup] = tab.id + updateStrategyCodeBlock() + + // Preserve scroll position + const delta = (current.getBoundingClientRect().top) - pos + window.scrollBy(0, delta) + }) + } + } + +document$.subscribe(function() { + console.log(document.title) + tabSync() +}) diff --git a/docs/userguide_strategies.md b/docs/userguide_strategies.md new file mode 100644 index 000000000..c31e68c74 --- /dev/null +++ b/docs/userguide_strategies.md @@ -0,0 +1,19 @@ +# Strategies + + +=== "Single Objective" + +=== "Multi Objective" + +===! "Single Task" + +=== "Multi Task" + +=== "Multi Fidelity" + +===! "Simple Domain" + +=== "Many categorical features" + + +
 
\ No newline at end of file
diff --git a/mkdocs.yaml b/mkdocs.yaml
index a6af3eb3d..7bd1d49cd 100644
--- a/mkdocs.yaml
+++ b/mkdocs.yaml
@@ -9,6 +9,7 @@ nav:
   - Notebook page: getting_started.ipynb
   - Examples: examples.md
   - Data Models vs Functional Components: data_models_functionals.md
+  - Strategies: userguide_strategies.md
   - Surrogate Models: userguide_surrogates.md
   - API Reference:
     - Domain: ref-domain.md
@@ -60,10 +61,18 @@ markdown_extensions:
   - pymdownx.details
   - pymdownx.highlight
   - pymdownx.inlinehilite
+  - pymdownx.tabbed:
+      alternate_style: true
+      slugify: !!python/object/apply:pymdownx.slugs.slugify {kwds: {case: lower}}
   - admonition
 
+extra_css:
+  - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@11.11.1/build/styles/default.min.css
+
 extra_javascript:
+  - https://cdn.jsdelivr.net/gh/highlightjs/cdn-release@latest/build/highlight.min.js
   - javascripts/mathjax.js
+  - javascripts/selectortabs.js
   - https://unpkg.com/mathjax@3/es5/tex-mml-chtml.js
 
 extra:

From 52f470a2feaf43318249a00906b43e58bec19764 Mon Sep 17 00:00:00 2001
From: TobyBoyne 
Date: Fri, 4 Apr 2025 14:36:38 +0100
Subject: [PATCH 2/2] Include surrogates in strategy tool; expand code example

---
 docs/javascripts/selectortabs.js | 108 +++++++++++++++++--------------
 docs/userguide_strategies.md     |  14 ++++
 2 files changed, 72 insertions(+), 50 deletions(-)

diff --git a/docs/javascripts/selectortabs.js b/docs/javascripts/selectortabs.js
index ee175f5ab..e55786c87 100644
--- a/docs/javascripts/selectortabs.js
+++ b/docs/javascripts/selectortabs.js
@@ -1,61 +1,66 @@
-const selectorTabStrategies = {
-    // map from tab labels to group names
-    "single-objective": "objective",
-    "multi-objective": "objective",
-    "single-task": "tasks",
-    "multi-task": "tasks",
-    "multi-fidelity": "tasks",
-    "simple-domain": "domain",
-    "many-categorical-features": "domain",
-}
+// set default strategy
+let problemDescription = new Map([
+    ["objective", "single-objective"],
+    ["tasks", "single-task"],
+    ["domain", "simple-domain"],
+])
 
-let strategies = {
-    "objective": "single-objective",
-    "tasks": "single-task",
-    "domain": "simple-domain",
-}
+const strategyMap = new Map([
+    ["single-objective__single-task__simple-domain", ["SoboStrategy", ""]],
+    ["single-objective__single-task__many-categorical-features", ["EntingStrategy", ""]],
+    ["single-objective__multi-task__simple-domain", ["SoboStrategy", "MultiTaskGPSurrogate"]],
+    ["single-objective__multi-fidelity__simple-domain", ["MultiFidelity", ""]],
 
-const getStrategyCode = () => {
-    const objective = strategies["objective"];
-    const tasks = strategies["tasks"];
-    const domain = strategies["domain"];
+    ["multi-objective__single-task__simple-domain", ["MoboStrategy", ""]],
+])
 
-    let strategyDataModel = ""
-    if (objective == "single-objective") {
-        if (tasks == "single-task") {
-            if (domain == "simple-domain") {
-                strategyDataModel = "SoboStrategy"
-            } else if (domain == "many-categorical-features") {
-                strategyDataModel = "EntingStrategy"
-            }
-        } else if (tasks == "multi-task") {
-            strategyDataModel = "SoboStrategy"
-        } else if (tasks == "multi-fidelity") {
-            strategyDataModel = "MultiFidelityStrategy"
-            // we define multi fidelity as a multi-task where you can query the other fidelities
-        }
-    } else if (objective == "multi-objective") {
-        if (tasks == "single-task" && domain == "simple-domain") {
-            strategyDataModel = "MoboStrategy"
-        }
+const getSurrogateCode = (surrogateDataModel) => {
+    if (surrogateDataModel === "") {
+        return ""
     }
-
-    return hljs.highlight(
-`# import the data model
-from bofire.data_models.strategies.api import ${strategyDataModel}
-import bofire.strategies.api as strategies
-
+    return `
+# define the surrogate data model
 surrogate_data_model = BotorchSurrogates(surrogates=[
-    MultiTaskGPSurrogate(
+    ${surrogateDataModel}(
         inputs=domain.inputs,
         outputs=domain.outputs,
     )
 ])
+`}
 
+const getStrategyComment = (strategyDataModel) => {
+    return (strategyDataModel !== "EntingStrategy") ? "" : `
+# the default GP surrogate is slow to optimize when many discrete features are present
+# the ENTMOOT model can optimize over domains with many categories`
+}
+
+const getStrategyCode = () => {
+    const dataModels = strategyMap.get([...problemDescription.values()].join("__"))
+    if (dataModels === undefined) {
+        return hljs.highlight("# There isn't currently a BoFire model that works for this problem.", {language: "python"})
+    }
+
+    const [strategyDataModel, surrogateDataModel] = dataModels
+    const requiresSurrogate = (surrogateDataModel !== "")
+    const surrogateImport = (!requiresSurrogate) ? "" : `
+from bofire.data_models.surrogates.api import ${surrogateDataModel}`
+
+    const strategyComment = getStrategyComment(strategyDataModel)
+    const surrogateCode = getSurrogateCode(surrogateDataModel)
+
+    return hljs.highlight(
+`from bofire.data_models.strategies.api import ${strategyDataModel} ${surrogateImport}
+import bofire.strategies.api as strategies
+${surrogateCode}
+# define the strategy data model ${strategyComment}
 strategy_data_model = ${strategyDataModel}(
-    domain=domain
-    surrogate=surrogate_data_model
+    domain=domain${!requiresSurrogate ? "" : `
+    surrogate=surrogate_data_model`}
 )
+
+# create an instance of the functional strategy
+# to understand the difference between data models and functional components,
+# see https://experimental-design.github.io/bofire/data_models_functionals/
 strategy = strategies.map(strategy_data_model)
 `, { language : "python"}
     )
@@ -71,12 +76,12 @@ const tabSync = () => {
     for (const tab of tabs) {
       tab.addEventListener("click", () => {
         const current = document.querySelector(`label[for=${tab.id}]`)
-        console.log(tab.id)
         const pos = current.getBoundingClientRect().top
         // const labels = document.querySelectorAll('.tabbed-set > label, .tabbed-alternate > .tabbed-labels > label')
         
-        const updatedGroup = selectorTabStrategies[tab.id]
-        strategies[updatedGroup] = tab.id
+        const updatedGroup = tab.parentElement.id
+        // const updatedGroup = selectorTabStrategies[tab.id]
+        problemDescription.set(updatedGroup, tab.id)
         updateStrategyCodeBlock()
 
         // Preserve scroll position
@@ -87,6 +92,9 @@ const tabSync = () => {
   }
 
 document$.subscribe(function() {
-    console.log(document.title)
+    ["objective", "tasks", "domain"].forEach(k => {
+        document.querySelector(`#${k}-marker`).nextElementSibling.setAttribute("id", k)
+    })
     tabSync()
+    updateStrategyCodeBlock()
 })
diff --git a/docs/userguide_strategies.md b/docs/userguide_strategies.md
index c31e68c74..4709334ad 100644
--- a/docs/userguide_strategies.md
+++ b/docs/userguide_strategies.md
@@ -1,16 +1,30 @@
 # Strategies
 
+
+
+In BoFire, a `Strategy` provides an interface for proposing new experiments. 
+Below, we provide a tool for suggesting the most appropriate strategy for your
+Bayesian optimization problem. Our tool is inspired by [honegumi](https://github.com/sgbaird/honegumi).
+
+
=== "Single Objective" === "Multi Objective" +
+ ===! "Single Task" === "Multi Task" === "Multi Fidelity" +
+ ===! "Simple Domain" === "Many categorical features"