Quick Start Guide
=================

This guide will get you up and running with MAPLE in just a few minutes.

Overview
--------

MAPLE uses a **dataset-centric architecture** where the ``FEPDataset`` object serves as the central hub for all data and predictions. The main workflow involves:

1. **Creating a dataset** - Load your FEP data into an ``FEPDataset`` object
2. **Fitting models** - Configure and fit one or more inference models
3. **Adding predictions** - Call ``add_predictions_to_dataset()`` to store results
4. **Analyzing results** - Access all predictions from the dataset

.. code-block:: text

   1. Create Dataset
      FEPDataset(your data)
            |
            v
   2. Fit Models
      VariationalEstimator | GaussianMixtureVI | CycleClosureCorrection | SpectralCorrection
            |
            v
   3. Add Predictions
      model.add_predictions_to_dataset()
            |
            v
   4. Access Results
      dataset.dataset_nodes  |  dataset.estimators

Basic Example
-------------

Here's a complete example of the MAPLE workflow:

.. code-block:: python

   from maple.dataset import FEPDataset
   from maple.models import VariationalEstimator, VariationalEstimatorConfig, PriorType, GuideType
   from maple.models import GaussianMixtureVI, GaussianMixtureVIConfig
   
   # =====================================================
   # STEP 1: Create the dataset (your central data hub)
   # =====================================================
   
   # Option A: Load from benchmark dataset
   dataset = FEPDataset(dataset_name="cdk8", sampling_time="5ns")
   
   # Option B: Load from your own DataFrames
   # dataset = FEPDataset(dataset_nodes=nodes_df, dataset_edges=edges_df)
   
   # Option C: Load from CSV files
   # dataset = FEPDataset(nodes_csv_path="nodes.csv", edges_csv_path="edges.csv")
   
   print(f"Loaded {len(dataset.dataset_nodes)} nodes and {len(dataset.dataset_edges)} edges")
   
   # =====================================================
   # STEP 2: Configure and fit the MAP model
   # =====================================================

   map_config = VariationalEstimatorConfig(
       learning_rate=0.01,
       num_steps=5000,
       prior_type=PriorType.NORMAL,
       guide_type=GuideType.AUTO_DELTA  # MAP inference
   )

   map_model = VariationalEstimator(config=map_config, dataset=dataset)
   map_model.fit()
   
   # =====================================================
   # STEP 3: Add predictions to the dataset
   # =====================================================
   
   map_model.add_predictions_to_dataset()
   
   # Now dataset.dataset_nodes has a new "MAP" column!
   print(dataset.dataset_nodes[['Name', 'Exp. DeltaG', 'MAP']].head())
   
   # =====================================================
   # STEP 4: Fit additional models (optional)
   # =====================================================
   
   # GMVI model with outlier detection
   gmvi_config = GaussianMixtureVIConfig(prior_std=5.0, outlier_prob=0.2)
   gmvi_model = GaussianMixtureVI(dataset=dataset, config=gmvi_config)
   gmvi_model.fit()
   gmvi_model.get_posterior_estimates()
   gmvi_model.add_predictions_to_dataset()
   
   # Now dataset has both "MAP" and "GMVI" columns!
   print(f"Applied estimators: {dataset.estimators}")
   # Output: ['MAP', 'GMVI']
   
   # =====================================================
   # STEP 5: Compare model performance
   # =====================================================
   
   from maple.graph_analysis import compute_simple_statistics
   
   exp_values = dataset.dataset_nodes['Exp. DeltaG'].values
   
   for estimator in dataset.estimators:
       pred_values = dataset.dataset_nodes[estimator].values
       stats = compute_simple_statistics(exp_values, pred_values)
       print(f"{estimator}: RMSE={stats['RMSE']:.3f}, R2={stats['R2']:.3f}")

Data Format
-----------

MAPLE expects FEP data in two DataFrames: **nodes** and **edges**.

Edge DataFrame
~~~~~~~~~~~~~~

Required columns:

* **Source** (or **Ligand1**): Source ligand name
* **Destination** (or **Ligand2**): Target ligand name
* **DeltaDeltaG** (or **FEP**): Relative binding free energy difference

Optional columns:

* **DeltaDeltaG Error**: Uncertainty from BAR/MBAR
* **CCC**: Cycle closure corrected values

.. code-block:: text

   +--------+-------------+-------------+------------------+-------+
   | Source | Destination | DeltaDeltaG | DeltaDeltaG Error| CCC   |
   +--------+-------------+-------------+------------------+-------+
   | molA   | molB        | -2.3        | 0.5              | -2.25 |
   | molB   | molC        | 1.1         | 0.4              | 1.08  |
   | molA   | molC        | -1.2        | 0.6              | -1.17 |
   +--------+-------------+-------------+------------------+-------+

Node DataFrame
~~~~~~~~~~~~~~

Required columns:

* **Name**: Ligand name
* **Exp. DeltaG**: Experimental binding free energy

.. code-block:: text

   +------+-------------+
   | Name | Exp. DeltaG |
   +------+-------------+
   | molA | -8.5        |
   | molB | -6.2        |
   | molC | -7.3        |
   +------+-------------+

Creating a Dataset
------------------

From Benchmark Data
~~~~~~~~~~~~~~~~~~~

MAPLE includes standard FEP benchmark datasets:

.. code-block:: python

   from maple.dataset import FEPDataset
   
   # Load a benchmark dataset
   dataset = FEPDataset(dataset_name="cdk8", sampling_time="5ns")
   
   # Available datasets: cdk8, cmet, eg5, hif2a, pfkfb3, shp2, syk, tnks2

From DataFrames
~~~~~~~~~~~~~~~

.. code-block:: python

   import pandas as pd
   from maple.dataset import FEPDataset
   
   # Create edge DataFrame
   edges_df = pd.DataFrame({
       'Source': ['molA', 'molB', 'molA'],
       'Destination': ['molB', 'molC', 'molC'],
       'DeltaDeltaG': [-2.3, 1.1, -1.2],
       'DeltaDeltaG Error': [0.5, 0.4, 0.6]
   })
   
   # Create node DataFrame
   nodes_df = pd.DataFrame({
       'Name': ['molA', 'molB', 'molC'],
       'Exp. DeltaG': [-8.5, -6.2, -7.3]
   })
   
   # Create dataset
   dataset = FEPDataset(dataset_nodes=nodes_df, dataset_edges=edges_df)

From Edges Only
~~~~~~~~~~~~~~~

If you only have edge data, MAPLE can derive node values:

.. code-block:: python

   from maple.dataset import FEPDataset
   
   # Dataset will automatically derive node values from edges
   dataset = FEPDataset(dataset_edges=edges_df)

Available Models
----------------

VariationalEstimator (MAP/VI/MLE)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``VariationalEstimator`` provides Maximum A Posteriori (MAP), Variational Inference (VI), or Maximum Likelihood (MLE) estimation:

.. code-block:: python

   from maple.models import VariationalEstimator, VariationalEstimatorConfig, PriorType, GuideType

   # MAP inference (point estimates)
   map_config = VariationalEstimatorConfig(
       prior_type=PriorType.NORMAL,
       guide_type=GuideType.AUTO_DELTA,
       learning_rate=0.01,
       num_steps=5000
   )
   map_model = VariationalEstimator(config=map_config, dataset=dataset)
   map_model.fit()
   map_model.add_predictions_to_dataset()  # Adds "MAP" column

   # VI inference (with uncertainties)
   vi_config = VariationalEstimatorConfig(
       prior_type=PriorType.NORMAL,
       guide_type=GuideType.AUTO_NORMAL,  # VI instead of MAP
       learning_rate=0.01,
       num_steps=5000
   )
   vi_model = VariationalEstimator(config=vi_config, dataset=dataset)
   vi_model.fit()
   vi_model.add_predictions_to_dataset()  # Adds "VI" and "VI_uncertainty" columns

GaussianMixtureVI (Outlier-Robust)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``GaussianMixtureVI`` uses a Gaussian mixture likelihood for outlier detection:

.. code-block:: python

   from maple.models import GaussianMixtureVI, GaussianMixtureVIConfig

   gmvi_config = GaussianMixtureVIConfig(
       prior_std=5.0,        # Prior std for node values
       normal_std=1.0,       # Std for normal edges
       outlier_std=3.0,      # Std for outlier edges
       outlier_prob=0.2,     # Probability of outlier
       n_epochs=2000
   )

   gmvi_model = GaussianMixtureVI(dataset=dataset, config=gmvi_config)
   gmvi_model.fit()
   gmvi_model.get_results()  # Required before add_predictions
   gmvi_model.add_predictions_to_dataset()  # Adds "GMVI" and "GMVI_uncertainty"

   # Get outlier probabilities for each edge
   outlier_probs = gmvi_model.compute_edge_outlier_probabilities()

SpectralCorrection (WSFC/SFC)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``SpectralCorrection`` uses the graph Laplacian pseudoinverse for a direct, closed-form solution:

.. code-block:: python

   from maple.models import SpectralCorrection, SpectralCorrectionConfig

   # WSFC: Weighted by edge uncertainties (precision = 1/sigma^2)
   wsfc_config = SpectralCorrectionConfig(use_weights=True)
   wsfc_model = SpectralCorrection(config=wsfc_config, dataset=dataset)
   wsfc_model.fit()  # Instant -- no iterative optimization
   wsfc_model.add_predictions_to_dataset()  # Adds "WSFC" and "WSFC_uncertainty"

   # SFC: Unweighted (equivalent to MLE)
   sfc_config = SpectralCorrectionConfig(use_weights=False)
   sfc_model = SpectralCorrection(config=sfc_config, dataset=dataset)
   sfc_model.fit()
   sfc_model.add_predictions_to_dataset()  # Adds "SFC" and "SFC_uncertainty"

Key Concepts
------------

The Dataset as Central Hub
~~~~~~~~~~~~~~~~~~~~~~~~~~

The ``FEPDataset`` object serves as the central hub:

.. code-block:: text

   Models (write predictions)
         |
         | add_predictions_to_dataset()
         v
   +------------------------------------------------------------------+
   |                  FEPDataset (Central Hub)                         |
   +------------------------------------------------------------------+
   |  dataset_nodes:  Name | Exp. DeltaG | MAP | VI | GMVI | ...      |
   |  dataset_edges:  Source | Dest | DeltaDeltaG | predictions        |
   |  estimators:     ['MAP', 'VI', 'GMVI', ...]                      |
   +------------------------------------------------------------------+
         |
         v
   Analysis (read predictions)

The add_predictions_to_dataset() Pattern
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

Every model has an ``add_predictions_to_dataset()`` method that:

1. Writes node predictions to ``dataset.dataset_nodes``
2. Writes edge predictions to ``dataset.dataset_edges``
3. Adds uncertainties if available
4. Registers the model in ``dataset.estimators``

.. code-block:: python

   # This pattern is the same for all models:
   model.fit()
   model.add_predictions_to_dataset()  # Writes results to dataset
   
   # Now access results from the dataset:
   print(dataset.dataset_nodes['MAP'])  # Node predictions
   print(dataset.dataset_edges['MAP'])  # Edge predictions

Performance Analysis
--------------------

After fitting models, analyze performance using the dataset:

.. code-block:: python

   from maple.graph_analysis import (
       compute_simple_statistics,
       compute_bootstrap_statistics
   )
   
   # Get experimental and predicted values
   exp_values = dataset.dataset_nodes['Exp. DeltaG'].values
   map_preds = dataset.dataset_nodes['MAP'].values
   
   # Simple statistics
   stats = compute_simple_statistics(exp_values, map_preds)
   print(f"RMSE: {stats['RMSE']:.3f} kcal/mol")
   print(f"R2: {stats['R2']:.3f}")
   print(f"Pearson r: {stats['r']:.3f}")
   
   # Bootstrap confidence intervals
   bootstrap_stats = compute_bootstrap_statistics(
       exp_values, map_preds, n_bootstrap=1000
   )
   print(f"RMSE 95% CI: [{bootstrap_stats['RMSE']['ci_lower']:.3f}, "
         f"{bootstrap_stats['RMSE']['ci_upper']:.3f}]")

Visualization
-------------

Create publication-quality plots:

.. code-block:: python

   from maple.graph_analysis import plot_node_correlation
   import matplotlib.pyplot as plt
   
   # Plot model predictions vs experimental data
   fig, axes = plt.subplots(1, len(dataset.estimators), figsize=(5*len(dataset.estimators), 5))
   
   exp_values = dataset.dataset_nodes['Exp. DeltaG'].values
   
   for ax, estimator in zip(axes, dataset.estimators):
       pred_values = dataset.dataset_nodes[estimator].values
       ax.scatter(exp_values, pred_values, alpha=0.7)
       
       # Add diagonal line
       min_val = min(exp_values.min(), pred_values.min())
       max_val = max(exp_values.max(), pred_values.max())
       ax.plot([min_val, max_val], [min_val, max_val], 'r--')
       
       ax.set_xlabel('Experimental (kcal/mol)')
       ax.set_ylabel(f'{estimator} Predicted (kcal/mol)')
       ax.set_title(f'{estimator} vs Experimental')
   
   plt.tight_layout()
   plt.savefig('model_comparison.pdf')

Next Steps
----------

* See :doc:`architecture` for detailed architecture documentation
* Check :doc:`tutorials` for step-by-step tutorials
* Explore :doc:`parameter_optimization` for systematic parameter studies
* Browse the :doc:`../api/models` for complete API documentation
* Review :doc:`../examples/basic_usage` for more examples
