Skip to content

Commit

Permalink
Add example EOS package and modify example config, update eos pkg create
Browse files Browse the repository at this point in the history
  • Loading branch information
aangelos28 committed Oct 25, 2024
1 parent 2448981 commit 0537588
Show file tree
Hide file tree
Showing 16 changed files with 337 additions and 7 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
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.
15 changes: 15 additions & 0 deletions user/example/tasks/score_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 ScoreMultiplicationTask(BaseTask):
async def _execute(
self,
devices: BaseTask.DevicesType,
parameters: BaseTask.ParametersType,
containers: BaseTask.ContainersType,
) -> BaseTask.OutputType:
analyzer = devices.get_all_by_type("analyzer")[0]
loss = analyzer.analyze_result(parameters["number"], parameters["product"])
output_parameters = {"loss": loss}

return output_parameters, None, None
21 changes: 21 additions & 0 deletions user/example/tasks/score_multiplication/task.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
type: Score Multiplication
description: Scores multiplication based on how close the product is to 1024 and how small the initial number is using an "analyzer" device.

device_types:
- analyzer

input_parameters:
number:
type: integer
unit: none
description: The number that was multiplied with some factors.
product:
type: integer
unit: none
description: The final product after multiplying with some factors.

output_parameters:
loss:
type: integer
unit: none
description: The multiplication loss. Captures how far the product is from 1024 and how large the initial number is.

0 comments on commit 0537588

Please sign in to comment.