PS C:\Users\heman\Desktop\Notes> & C:/Users/heman/AppData/Local/Programs/Python/Python312/python.exe c:/Users/heman/Desktop/Notes/2024notes/LLMs/distributiontorchmodule.py
Traceback (most recent call last):
  File "c:\Users\heman\Desktop\Notes\2024notes\LLMs\distributiontorchmodule.py", line 392, in <module>
    main()
  File "c:\Users\heman\Desktop\Notes\2024notes\LLMs\distributiontorchmodule.py", line 368, in main
    component.render()
  File "C:\Users\heman\AppData\Local\Programs\Python\Python312\Lib\site-packages\gradio\blocks.py", line 190, in render
    raise DuplicateBlockError(
gradio.exceptions.DuplicateBlockError: A block with id: 12 has already been rendered in the current Blocks.
Traceback (most recent call last):
  File "c:\Users\heman\Desktop\Notes\2024notes\LLMs\distributiontorchmodule.py", line 392, in <module>
    main()
  File "c:\Users\heman\Desktop\Notes\2024notes\LLMs\distributiontorchmodule.py", line 368, in main
    component.render()
  File "C:\Users\heman\AppData\Local\Programs\Python\Python312\Lib\site-packages\gradio\blocks.py", line 190, in render
    raise DuplicateBlockError(
gradio.exceptions.DuplicateBlockError: A block with id: 12 has already been rendered in the current Blocks.
    main()
  File "c:\Users\heman\Desktop\Notes\2024notes\LLMs\distributiontorchmodule.py", line 368, in main
    component.render()
  File "C:\Users\heman\AppData\Local\Programs\Python\Python312\Lib\site-packages\gradio\blocks.py", line 190, in render
    raise DuplicateBlockError(
gradio.exceptions.DuplicateBlockError: A block with id: 12 has already been rendered in the current Blocks.
gradio.exceptions.DuplicateBlockError: A block with id: 12 has already been rendered in the current Blocks.
PS C:\Users\heman\Desktop\Notes>

import torch
import gradio as gr
import plotly.graph_objs as go
from typing import Any, Callable, Dict, List, Optional, Tuple, Union
import numpy as np

# ----------------------------
# Distribution Information
# ----------------------------

class DistributionInfo:
    """
    A class to hold information about each distribution.
    """

    def __init__(
        self,
        cls: Callable[..., torch.distributions.Distribution],
        parameters: Dict[str, Dict[str, Any]],
        support: Optional[Tuple[float, float]] = None,
    ) -> None:
        """
        Initializes the DistributionInfo.

        Args:
            cls: The PyTorch distribution class.
            parameters: A dictionary mapping parameter names to their type and default value.
            support: The support of the distribution as a tuple (min, max).
        """
        self.cls = cls
        self.parameters = parameters
        self.support = support

# Mapping of distribution names to their corresponding torch.distributions classes and parameters
DISTRIBUTIONS: Dict[str, DistributionInfo] = {
    "Bernoulli": DistributionInfo(
        cls=torch.distributions.Bernoulli,
        parameters={
            "probs": {"type": "float", "default": 0.5},
        },
        support=(0, 1),
    ),
    "Beta": DistributionInfo(
        cls=torch.distributions.Beta,
        parameters={
            "concentration1": {"type": "float", "default": 1.0},
            "concentration0": {"type": "float", "default": 1.0},
        },
        support=(0, 1),
    ),
    "Binomial": DistributionInfo(
        cls=torch.distributions.Binomial,
        parameters={
            "total_count": {"type": "int", "default": 10},
            "probs": {"type": "float", "default": 0.5},
        },
        support=(0, None),
    ),
    "Categorical": DistributionInfo(
        cls=torch.distributions.Categorical,
        parameters={
            "probs": {"type": "list", "default": [0.25, 0.25, 0.25, 0.25]},
        },
        support=(0, None),
    ),
    "Normal": DistributionInfo(
        cls=torch.distributions.Normal,
        parameters={
            "loc": {"type": "float", "default": 0.0},
            "scale": {"type": "float", "default": 1.0},
        },
        support=(-np.inf, np.inf),
    ),
    # Add more distributions here following the same structure
    # ...
}

# ----------------------------
# Utility Functions
# ----------------------------

def create_distribution(
    distribution_name: str, params: Dict[str, Any]
) -> torch.distributions.Distribution:
    """
    Creates a PyTorch distribution object based on the distribution name and parameters.

    Args:
        distribution_name: The name of the distribution.
        params: A dictionary of parameters for the distribution.

    Returns:
        An instance of a PyTorch distribution.

    Raises:
        ValueError: If the distribution is not supported or parameters are invalid.
    """
    if distribution_name not in DISTRIBUTIONS:
        raise ValueError(f"Unsupported distribution: {distribution_name}")

    dist_info = DISTRIBUTIONS[distribution_name]
    try:
        # Convert list parameters to torch tensors if necessary
        for param, value in params.items():
            if dist_info.parameters[param]["type"] == "list":
                params[param] = torch.tensor(value, dtype=torch.float32)
        distribution = dist_info.cls(**params)
    except Exception as e:
        raise ValueError(f"Error creating distribution: {e}")

    return distribution

def generate_x_values(
    support: Tuple[float, float], num_points: int = 1000
) -> torch.Tensor:
    """
    Generates x values over the support of the distribution.

    Args:
        support: A tuple indicating the range (min, max).
        num_points: Number of points to generate.

    Returns:
        A tensor of x values.
    """
    min_val, max_val = support
    if min_val == -np.inf:
        min_val = -10
    if max_val == np.inf:
        max_val = 10
    return torch.linspace(min_val, max_val, steps=num_points)

def plot_distribution(
    distribution: torch.distributions.Distribution,
    plot_pdf: bool,
    plot_cdf: bool,
) -> Tuple[go.Figure, str]:
    """
    Generates Plotly figures for PDF and/or CDF of the distribution.

    Args:
        distribution: The PyTorch distribution object.
        plot_pdf: Whether to plot PDF.
        plot_cdf: Whether to plot CDF.

    Returns:
        A tuple containing the Plotly figure and an error message if any.
    """
    try:
        # Determine support for x-axis
        if hasattr(distribution, "support"):
            support = distribution.support.lower_bound, distribution.support.upper_bound
        else:
            support = (-10, 10)

        x = generate_x_values(support)
        x_np = x.numpy()

        fig = go.Figure()
        if plot_pdf:
            pdf_values = distribution.log_prob(x).exp().numpy()
            fig.add_trace(
                go.Scatter(
                    x=x_np,
                    y=pdf_values,
                    mode="lines",
                    name="PDF",
                )
            )
        if plot_cdf:
            cdf_values = distribution.cdf(x).numpy()
            fig.add_trace(
                go.Scatter(
                    x=x_np,
                    y=cdf_values,
                    mode="lines",
                    name="CDF",
                )
            )
        fig.update_layout(
            title="Distribution Plot",
            xaxis_title="x",
            yaxis_title="Probability",
            template="plotly_white",
        )
        return fig, ""
    except Exception as e:
        return go.Figure(), f"Error generating plot: {e}"

def validate_and_parse_params(
    distribution_name: str, inputs: List[Any]
) -> Tuple[Optional[Dict[str, Any]], Optional[str]]:
    """
    Validates and parses the input parameters for the selected distribution.

    Args:
        distribution_name: The name of the distribution.
        inputs: A list of input parameters.

    Returns:
        A tuple containing the parsed parameters dictionary and an error message if any.
    """
    dist_info = DISTRIBUTIONS.get(distribution_name)
    if not dist_info:
        return None, f"Unsupported distribution: {distribution_name}"

    parsed_params = {}
    for idx, (param, prop) in enumerate(dist_info.parameters.items()):
        try:
            value = inputs[idx]
            if prop["type"] == "float":
                parsed_params[param] = float(value)
            elif prop["type"] == "int":
                parsed_params[param] = int(value)
            elif prop["type"] == "list":
                # Expecting a comma-separated string of numbers
                parsed_params[param] = [float(x.strip()) for x in value.split(",")]
            else:
                parsed_params[param] = value
        except (ValueError, IndexError) as e:
            return None, f"Invalid value for parameter '{param}': {inputs[idx]}"
    return parsed_params, None

def get_parameter_components(
    distribution_name: str,
) -> List[gr.components.Component]:
    """
    Generates a list of Gradio input components based on the distribution's parameters.

    Args:
        distribution_name: The name of the distribution.

    Returns:
        A list of Gradio components corresponding to the distribution's parameters.
    """
    dist_info = DISTRIBUTIONS.get(distribution_name)
    if not dist_info:
        return []

    components = []
    for param, prop in dist_info.parameters.items():
        if prop["type"] == "float":
            components.append(
                gr.Number(
                    label=param.replace("_", " ").capitalize(),
                    value=prop["default"],
                    precision=4,
                )
            )
        elif prop["type"] == "int":
            components.append(
                gr.Number(
                    label=param.replace("_", " ").capitalize(),
                    value=prop["default"],
                    precision=0,
                )
            )
        elif prop["type"] == "list":
            components.append(
                gr.Textbox(
                    label=f"{param.replace('_', ' ').capitalize()} (comma separated)",
                    value=",".join(map(str, prop["default"])),
                )
            )
        else:
            components.append(
                gr.Textbox(
                    label=param.replace("_", " ").capitalize(),
                    value=str(prop["default"]),
                )
            )
    return components

# ----------------------------
# Main Plot Function
# ----------------------------

def plot_function(
    distribution_name: str,
    plot_pdf: bool,
    plot_cdf: bool,
    *args,  # Accept additional positional arguments
) -> Union[go.Figure, str]:
    """
    The main function to create distribution and generate plots.

    Args:
        distribution_name: The name of the distribution.
        plot_pdf: Whether to plot PDF.
        plot_cdf: Whether to plot CDF.
        *args: Additional parameters for the distribution.

    Returns:
        A Plotly figure or an error message.
    """
    try:
        # Validate and parse parameters
        params, error = validate_and_parse_params(distribution_name, list(args))
        if error:
            return error

        # Create distribution
        distribution = create_distribution(distribution_name, params)

        # Generate plot
        fig, plot_error = plot_distribution(distribution, plot_pdf, plot_cdf)
        if plot_error:
            return plot_error

        return fig

    except Exception as e:
        return f"An error occurred: {e}"

# ----------------------------
# Gradio Interface Setup
# ----------------------------

def main() -> None:
    """
    Sets up and launches the Gradio interface.
    """
    with gr.Blocks() as demo:
        gr.Markdown("# PyTorch Distributions Explorer")
        gr.Markdown(
            """
            Select a distribution, provide its parameters, and visualize its PDF and CDF.
            """
        )

        with gr.Row():
            distribution = gr.Dropdown(
                choices=list(DISTRIBUTIONS.keys()),
                value="Normal",
                label="Distribution",
            )
            plot_pdf = gr.Checkbox(
                label="Plot PDF", value=True
            )
            plot_cdf = gr.Checkbox(
                label="Plot CDF", value=True
            )

        with gr.Row():
            parameter_inputs = gr.Column()

        with gr.Row():
            plot_output = gr.Plot(label="Distribution Plot")

        def on_distribution_change(name: str):
            """
            Callback function to update parameter input fields when the distribution changes.
            """
            return get_parameter_components(name)

        # Update parameter inputs when distribution changes
        distribution.change(
            on_distribution_change,
            inputs=distribution,
            outputs=parameter_inputs,
        )

        # Initialize parameter inputs with the default distribution
        initial_parameter_components = get_parameter_components(distribution.value)
        with parameter_inputs:
            # Initialize Column with initial components
            for component in initial_parameter_components:
                component.render()

        # Define the submit button
        submit = gr.Button("Generate Plot")

        # Define an intermediary function to handle dynamic inputs
        def submit_click_handler(distribution_name, plot_pdf_val, plot_cdf_val, *params):
            return plot_function(distribution_name, plot_pdf_val, plot_cdf_val, *params)

        # Define inputs for the plot_function
        submit.click(
            fn=submit_click_handler,
            inputs=[distribution, plot_pdf, plot_cdf] + initial_parameter_components,
            outputs=plot_output,
        )

    # Launch the Gradio app
    demo.launch()

# ----------------------------
# Entry Point
# ----------------------------

if __name__ == "__main__":
    main()