Skip to content

Commit

Permalink
Add example EOS package and modify example config, update eos pkg cre…
Browse files Browse the repository at this point in the history
…ate, update docs
  • Loading branch information
aangelos28 committed Oct 25, 2024
1 parent 2448981 commit efc6960
Show file tree
Hide file tree
Showing 18 changed files with 349 additions and 8 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -170,4 +170,5 @@ cython_debug/
/config.yml

/user/*
!/user/example/
!/user/.gitkeep
11 changes: 5 additions & 6 deletions config.example.yml
Original file line number Diff line number Diff line change
@@ -1,25 +1,24 @@
user_dir: ./user
labs:
- lab1
- lab2
- multiplication_lab
experiments:
- experiment1
- experiment2
- optimize_multiplication

log_level: INFO

# EOS orchestrator's internal web API server configuration
web_api:
host: localhost
port: 8070

# EOS database configuration
# EOS database (MongoDB) configuration
db:
host: localhost
port: 27017
username: ""
password: ""

# EOS file database configuration
# EOS file database (MinIO) configuration
file_db:
host: localhost
port: 9004
Expand Down
6 changes: 5 additions & 1 deletion docs/user-guide/configuration.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,8 @@ After installation, you need to configure external services such as MongoDB and

1. Configure External Services
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We provide a Docker Compose file that can run the external services.
We provide a Docker Compose file that can run the external services. You do not need to install external services
manually, just provide configuration values and Docker Compose will take care of the rest.

Copy the example environment file:

Expand All @@ -26,3 +27,6 @@ Copy the example configuration file:
cp config.example.yml config.yml
Edit `config.yml`. Ensure that credentials are provided for the MongoDB and MinIO services.

By default, EOS loads the "multiplication_lab" laboratory and the "optimize_multiplication" experiment from an example
EOS package. Feel free to change this.
7 changes: 7 additions & 0 deletions docs/user-guide/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,8 @@ We provide a Docker Compose file that can set up all of these services for you.
^^^^^^^^^^^^^^
PDM is used as the project manager for EOS, making it easier to install dependencies and build it.

See the `PDM documentation <https://pdm-project.org/en/latest/>`_ for more information or if you encounter any issues.

.. tab-set::

.. tab-item:: Linux/Mac
Expand All @@ -40,6 +42,11 @@ PDM is used as the project manager for EOS, making it easier to install dependen
3. Make a Virtual Environment
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
We create a virtual environment to isolate the dependencies of EOS from the rest of the system. The virtual environment
is created in a ``env`` directory inside the EOS repository directory. Feel free to use PDM to manage the virtual
environment instead. Other sections of the documentation will assume that you are using a virtual environment located
inside the EOS repository directory.

.. code-block:: shell
cd eos # Navigate to the cloned repository
Expand Down
8 changes: 7 additions & 1 deletion eos/cli/pkg_cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,18 @@ def create_package(
) -> None:
"""Create a new package with the specified name in the user directory."""
package_dir = Path(user_dir) / name
subdirs = ["common", "devices", "tasks", "labs", "experiments"]
subdirs = ["devices", "tasks", "labs", "experiments"]

try:
package_dir.mkdir(parents=True, exist_ok=False)
for subdir in subdirs:
(package_dir / subdir).mkdir()

# Create README.md with just the package name
readme_content = f"# {name}"
readme_path = package_dir / "README.md"
readme_path.write_text(readme_content)

typer.echo(f"Successfully created package '{name}' in {package_dir}")
except FileExistsError:
typer.echo(f"Error: Package '{name}' already exists in {user_dir}", err=True)
Expand Down
121 changes: 121 additions & 0 deletions eos/tasks/entities/test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import asyncio
from dataclasses import asdict
from datetime import datetime, timezone
from eos.persistence.async_db_interface import AsyncDbInterface
from eos.persistence.service_credentials import ServiceCredentials
from eos.tasks.entities.task import Task, TaskInput, TaskOutput, TaskStatus, TaskDeviceConfig, TaskModel
from eos.experiments.entities.experiment import (
Experiment,
ExperimentStatus,
ExperimentExecutionParameters,
ExperimentModel,
)
from sqlalchemy.ext.asyncio import AsyncSession


async def main():
# Initialize the database interface
db = AsyncDbInterface(
db_credentials=ServiceCredentials(
host="localhost",
port=5432,
username="eos-user",
password="eos-password",
),
db_name="eos",
)

# Create tables
await db.create_tables()

async with db.session_provider.get_session() as session:
# Create a pydantic Experiment
pydantic_experiment = Experiment(
id="exp-001",
type="analysis_experiment",
execution_parameters=ExperimentExecutionParameters(resume=False),
status=ExperimentStatus.CREATED,
labs=["lab-001"],
dynamic_parameters={"param1": {"value": "test"}},
metadata={"created_by": "user123"},
created_at=datetime.now(timezone.utc),
)

print("Pydantic Experiment:")
print(pydantic_experiment.model_dump())

# Convert pydantic Experiment to ExperimentModel
experiment_model = ExperimentModel(
id=pydantic_experiment.id,
type=pydantic_experiment.type,
execution_parameters=pydantic_experiment.execution_parameters.model_dump(),
status=pydantic_experiment.status,
labs=pydantic_experiment.labs,
running_tasks=pydantic_experiment.running_tasks,
completed_tasks=pydantic_experiment.completed_tasks,
dynamic_parameters=pydantic_experiment.dynamic_parameters,
experiment_metadata=pydantic_experiment.metadata,
start_time=pydantic_experiment.start_time,
end_time=pydantic_experiment.end_time,
created_at=pydantic_experiment.created_at,
)

# Add experiment to session and flush
session.add(experiment_model)
await session.flush()

print(f"Experiment {experiment_model.id} has been created and stored in the database.")

# Create a pydantic Task associated with the experiment
pydantic_task = Task(
id="task-001",
type="analysis",
experiment_id=pydantic_experiment.id,
devices=[TaskDeviceConfig(id="device-001", lab_id="lab-001")],
input=TaskInput(parameters={"param1": "value1"}),
output=TaskOutput(),
status=TaskStatus.CREATED,
metadata={"created_by": "user123"},
created_at=datetime.now(timezone.utc),
)

print("\nPydantic Task:")
print(pydantic_task.model_dump())

# Convert pydantic Task to TaskModel
task_model = TaskModel(
id=pydantic_task.id,
type=pydantic_task.type,
experiment_id=pydantic_task.experiment_id,
devices=[asdict(device) for device in pydantic_task.devices],
input=pydantic_task.input.model_dump(),
output=pydantic_task.output.model_dump(),
status=pydantic_task.status.value,
task_metadata=pydantic_task.metadata,
start_time=pydantic_task.start_time,
end_time=pydantic_task.end_time,
created_at=pydantic_task.created_at,
)

# Add task to session and flush
session.add(task_model)
await session.flush()

print(f"Task {task_model.id} has been created and stored in the database.")

# Verify that both experiment and task were stored by querying them
stored_experiment = await session.get(ExperimentModel, experiment_model.id)
print(f"\nRetrieved experiment: {stored_experiment.id}, status: {stored_experiment.status}")

stored_task = await session.get(TaskModel, task_model.id)
print(f"Retrieved task: {stored_task.id}, status: {stored_task.status}")

# Commit the transaction
await session.commit()

# Close the database connection
await db.close()


if __name__ == "__main__":
asyncio.run(main())
17 changes: 17 additions & 0 deletions user/example/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Example EOS Package
This is a very simple EOS package that implements an experiment for finding the smallest number that when multiplied by
two factors is as close as possible to 1024.

## Experiments
The package contains the **optimize_multiplication** experiment which works as explained above.

## Laboratories
The package defines a very basic laboratory containing a "multiplier" and an "analyzer" device.

## Devices
1. **Multiplier**: Provides a function for multiplying two numbers.
2. **Analyzer**: Provides a function for producing a score on how close we are to the objective of the experiment.

## Tasks
1. **Multiply**: Multiplies two numbers using the multiplier device.
2. **Score Multiplication**: Scores the multiplication using the analyzer device.
19 changes: 19 additions & 0 deletions user/example/devices/analyzer/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Any

from eos.devices.base_device import BaseDevice


class AnalyzerDevice(BaseDevice):
"""Analyzes the multiplication result to produce a loss."""

async def _initialize(self, initialization_parameters: dict[str, Any]) -> None:
pass

async def _cleanup(self) -> None:
pass

async def _report(self) -> dict[str, Any]:
pass

def analyze_result(self, number: int, product: int) -> int:
return number + 100 * abs(product - 1024)
2 changes: 2 additions & 0 deletions user/example/devices/analyzer/device.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
type: analyzer
description: A device for analyzing the result of the multiplication of some numbers and computing a loss.
19 changes: 19 additions & 0 deletions user/example/devices/multiplier/device.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from typing import Any

from eos.devices.base_device import BaseDevice


class MultiplierDevice(BaseDevice):
"""Multiplies two numbers."""

async def _initialize(self, initialization_parameters: dict[str, Any]) -> None:
pass

async def _cleanup(self) -> None:
pass

async def _report(self) -> dict[str, Any]:
pass

def multiply(self, a: int, b: int) -> int:
return a * b
2 changes: 2 additions & 0 deletions user/example/devices/multiplier/device.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
type: multiplier
description: A device for multiplying two numbers
35 changes: 35 additions & 0 deletions user/example/experiments/optimize_multiplication/experiment.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
type: optimize_multiplication
description: An experiment for finding the smallest number that when multiplied by two factors yields 1024

labs:
- multiplication_lab

tasks:
- id: mult_1
type: Multiplication
devices:
- lab_id: multiplication_lab
id: multiplier
parameters:
number: eos_dynamic
factor: eos_dynamic

- id: mult_2
type: Multiplication
devices:
- lab_id: multiplication_lab
id: multiplier
dependencies: [ mult_1 ]
parameters:
number: mult_1.product
factor: eos_dynamic

- id: score_multiplication
type: Score Multiplication
devices:
- lab_id: multiplication_lab
id: analyzer
dependencies: [ mult_1, mult_2 ]
parameters:
number: mult_1.number
product: mult_2.product
27 changes: 27 additions & 0 deletions user/example/experiments/optimize_multiplication/optimizer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
from bofire.data_models.acquisition_functions.acquisition_function import qLogNEI
from bofire.data_models.enum import SamplingMethodEnum
from bofire.data_models.features.continuous import ContinuousOutput
from bofire.data_models.features.discrete import DiscreteInput
from bofire.data_models.objectives.identity import MinimizeObjective

from eos.optimization.abstract_sequential_optimizer import AbstractSequentialOptimizer
from eos.optimization.sequential_bayesian_optimizer import BayesianSequentialOptimizer


def eos_create_campaign_optimizer() -> tuple[dict, type[AbstractSequentialOptimizer]]:
constructor_args = {
"inputs": [
DiscreteInput(key="mult_1.number", values=list(range(2, 34))),
DiscreteInput(key="mult_1.factor", values=list(range(2, 18))),
DiscreteInput(key="mult_2.factor", values=list(range(2, 18))),
],
"outputs": [
ContinuousOutput(key="score_multiplication.loss", objective=MinimizeObjective(w=1.0)),
],
"constraints": [],
"acquisition_function": qLogNEI(),
"num_initial_samples": 5,
"initial_sampling_method": SamplingMethodEnum.SOBOL,
}

return constructor_args, BayesianSequentialOptimizer
10 changes: 10 additions & 0 deletions user/example/labs/multiplication_lab/lab.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
type: multiplication_lab
description: An example laboratory for testing multiplication

devices:
multiplier:
type: multiplier
computer: eos_computer
analyzer:
type: analyzer
computer: eos_computer
15 changes: 15 additions & 0 deletions user/example/tasks/multiplication/task.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from eos.tasks.base_task import BaseTask


class MultiplicationTask(BaseTask):
async def _execute(
self,
devices: BaseTask.DevicesType,
parameters: BaseTask.ParametersType,
containers: BaseTask.ContainersType,
) -> BaseTask.OutputType:
multiplier = devices.get_all_by_type("multiplier")[0]
product = multiplier.multiply(parameters["number"], parameters["factor"])
output_parameters = {"product": product}

return output_parameters, None, None
21 changes: 21 additions & 0 deletions user/example/tasks/multiplication/task.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type: Multiplication
description: This task takes a number and a factor and multiplies them together using a "multiplier" device.

device_types:
- multiplier

input_parameters:
number:
type: integer
unit: none
description: The number to multiply.
factor:
type: integer
unit: none
description: The factor to multiply the number by.

output_parameters:
product:
type: integer
unit: none
description: The product of the number and the factor.
Loading

0 comments on commit efc6960

Please sign in to comment.