Skip to content

Contributing to Optiscope

Contributions are welcome! This guide outlines how you can contribute to the project.

How to Contribute

  1. New File Formats: Add handlers for solver-specific formats (NOMAD, Pymoo, etc.)
  2. Analysis Methods: Implement MCDA algorithms, metrics, etc.
  3. Page Modules: Create new visualization pages for the Dash app.
  4. Documentation: Improve docs, add examples, write tutorials.

Adding a New Page Module

The Optiscope Dash application is modular, allowing for the easy addition of new pages. Each page is a self-contained module that inherits from the PageModule base class. There are two main ways to add a new page: by developing it directly within the Optiscope project or by creating an external library that plugs into the application.

1. Creating the Page Module

First, you need to create a class that inherits from optiscope.dash_app.pages._base.PageModule. This class defines the page's metadata and layout.

Here is the basic structure of a page module file (e.g., page.py):

from dash import html
from optiscope.dash_app.pages._base import PageModule

class MyNewPage(PageModule):
    name = "My New Page"
    path = "/my-new-page"
    icon = "fas fa-rocket"  # Example: Font Awesome icon
    description = "A short description of what this page does."
    requires_data = True  # Set to False if the page doesn't need optimization data
    category = "Analysis" # Optional category for navigation

    def layout(self, **kwargs) -> html.Div:
        return html.Div([
            html.H1(self.name),
            html.P(self.description)
        ])

    def register_callbacks(self, app=None):
        # Register any Dash callbacks here
        pass

# Create an instance of your page module
page_module = MyNewPage()

2. Integrating the Page

You can integrate your new page using one of the following methods:

Method A: Developing Within the Optiscope Project

This is the simplest method for contributing a page directly to the core project. The application's page discovery mechanism (discover_local_pages in optiscope/dash_app/core/page_registry.py) automatically finds and registers new pages that follow a specific structure.

  1. Create a directory for your page inside optiscope/dash_app/pages/. The directory name should be descriptive (e.g., my_new_page).

    optiscope/
    └── dash_app/
        └── pages/
            └── my_new_page/
    

  2. Create a page.py file inside your new directory (optiscope/dash_app/pages/my_new_page/page.py).

  3. Define your PageModule subclass in page.py and instantiate it as page_module, as shown in the example above.

The application will automatically detect and register your page on startup.

Method B: Creating an External Library (Plugin)

This method is ideal for creating extensions or plugins that can be installed as separate Python packages. The page is discovered via entry points defined in your library's pyproject.toml.

  1. Set up your library structure. It can be structured however you like, but it must contain your PageModule implementation.

  2. Define an entry point in your library's pyproject.toml under the optiscope.pages group. This tells Optiscope where to find your page module.

    [project.entry-points."optiscope.pages"]
    my_page_entry_point = "my_plugin.pages:page_module"
    
    - my_page_entry_point is a unique name for your entry point. - my_plugin.pages:page_module is the import path to the instance of your PageModule subclass.

The discover_installed_package_pages function in optiscope/dash_app/core/page_registry.py will find any installed packages with this entry point and register the pages automatically.

Adding Custom File Formats

You can add a new file handler in two ways: by developing it directly within the Optiscope project or by creating an external library that plugs into the application.

Method A: Registering within Optiscope

Create a new handler by subclassing BaseFormatHandler and then register it with the global registry:

from optiscope.io import BaseFormatHandler, get_global_registry

class MyFormatHandler(BaseFormatHandler):
    format_name = "MyFormat"
    file_extensions = [".myformat"]
    priority = 15

    @classmethod
    def can_handle(cls, filepath):
        # Detection logic
        return filepath.suffix == ".myformat"

    def read(self, filepath, **kwargs):
        # Read and return OptimizationResult
        ...

    def write(self, result, filepath, **kwargs):
        # Write OptimizationResult to file
        ...

# Register the handler
registry = get_global_registry()
registry.register(MyFormatHandler)

Method B: Creating an External Library (Plugin)

This method is ideal for creating extensions or plugins that can be installed as separate Python packages. The file handler is discovered via entry points defined in your library's pyproject.toml.

  1. Set up your library structure. It can be structured however you like, but it must contain your BaseFormatHandler implementation.

  2. Define an entry point in your library's pyproject.toml under the optiscope.io.handlers group. This tells Optiscope where to find your file handler.

    [project.entry-points."optiscope.io.handlers"]
    my_handler_entry_point = "my_plugin.handlers:MyFormatHandler"
    
    - my_handler_entry_point is a unique name for your entry point. - my_plugin.handlers:MyFormatHandler is the import path to your BaseFormatHandler subclass.

The discover_handlers function in optiscope/io/registry.py will find any installed packages with this entry point and register the handlers automatically.