# spopt.locate.PDispersion

class spopt.locate.PDispersion(name: str, problem: LpProblem, p_facilities: int)

  Implement the \(p\)-dispersion optimization model and solve it [Kub87]. The \(p\)-dispersion problem, as adapted from [MKH12], can be formulated as:

  \[\begin{split}\begin{array}{lllll} \displaystyle \textbf{Maximize} & \displaystyle D && & (1) \\ \displaystyle \textbf{Subject To} & \displaystyle \sum_{i \in I}{Y_i} = p && & (2) \\ & D \leq d_{ij} + M (2 - Y_{i} - Y_{j}) && \forall i \in I \quad \forall j > i & (3) \\ & Y_i \in \{0, 1\} && \forall i \in I & (4) \\ & && & \\ \displaystyle \textbf{Where} && i, j & = & \textrm{index of potential facility sites in set } I \\ && p & = & \textrm{the number of facilities to be sited} \\ && d_{ij} & = & \textrm{shortest distance or travel time between locations } i \textrm{ and } j \\ && D & = & \textrm{minimum distance between any two sited facilities } i \textrm{ and } j \\ && M & = & \textrm{some large number; such that } M \geq \max_{ij}\{d_{ij}\} \\ && Y_i & = & \begin{cases} 1, \textrm{if a facility is sited at location } i \\ 0, \textrm{otherwise} \\ \end{cases} \\ \end{array}\end{split}\]
  Parameters:
    name `python:str`

      The problem name.

    problem `pulp.LpProblem`

      A `pulp` instance of an optimization model that contains constraints, variables, and an objective function.

  Attributes:
    name `python:str`

      The problem name.

    problem `pulp.LpProblem`

      A `pulp` instance of an optimization model that contains constraints, variables, and an objective function.

  __init__(name: str, problem: LpProblem, p_facilities: int)

    Initialize.

    Parameters:
      name `python:str`

        The desired name for the model.

  Methods

  `__init__`(name, problem, p_facilities) | Initialize.
  `check_status`() | Ensure a model is solved.
  `from_cost_matrix`(cost_matrix, p_facilities) | Create a `PDispersion` object based on a cost matrix.
  `from_geodataframe`(gdf_fac, facility_col, ...) | Create a `PDispersion` object based on a geodataframe.
  `solve`(solver) | Solve the `PDispersion` model.

  classmethod from_cost_matrix(cost_matrix: array, p_facilities: int, predefined_facilities_arr: array = None, name: str = 'p-dispersion')

    Create a `PDispersion` object based on a cost matrix.

    Parameters:
      cost_matrix `np.array`

        A cost matrix in the form of a 2D array between origins and destinations.

      p_facilities `python:int`

        The number of facilities to be located.

      predefined_facilities_arr `numpy.array` (default `python:None`)

        A binary 1D array of service facilities that must appear in the solution. For example, consider 3 facilites `['A', 'B', 'C']`. If facility `'B'` must be in the model solution, then the passed in array should be `[0, 1, 0]`.

      name `python:str` (default ‘p-dispersion’)

        The name of the problem.

    Returns:
      `spopt.locate.p_dispersion.PDispersion`

    Examples

        >>> from spopt.locate.p_dispersion import PDispersion
        >>> from spopt.locate.util import simulated_geo_points
        >>> import geopandas
        >>> import pulp
        >>> import spaghetti

    Create a regular lattice.

        >>> lattice = spaghetti.regular_lattice((0, 0, 10, 10), 9, exterior=True)
        >>> ntw = spaghetti.Network(in_data=lattice)
        >>> streets = spaghetti.element_as_gdf(ntw, arcs=True)
        >>> streets_buffered = geopandas.GeoDataFrame(
        ...     geopandas.GeoSeries(streets["geometry"].buffer(0.2).unary_union),
        ...     crs=streets.crs,
        ...     columns=["geometry"]
        ... )

    Simulate points about the lattice.

        >>> facility_points = simulated_geo_points(streets_buffered, needed=5, seed=6)

    Snap the points to the network of lattice edges.

        >>> ntw.snapobservations(facility_points, "facilities", attribute=True)
        >>> facilities_snapped = spaghetti.element_as_gdf(
        ...     ntw, pp_name="facilities", snapped=True
        ... )

    Calculate the cost matrix from origins to destinations. Origins and destinations are both `'facilities'` in this case.

        >>> cost_matrix = ntw.allneighbordistances(
        ...    sourcepattern=ntw.pointpatterns["facilities"],
        ...    destpattern=ntw.pointpatterns["facilities"]
        ... )

    Create and solve a `PDispersion` instance from the cost matrix.

        >>> pdispersion_from_cost_matrix = PDispersion.from_cost_matrix(
        ...     cost_matrix, p_fac=2
        ... )
        >>> pdispersion_from_cost_matrix = pdispersion_from_cost_matrix.solve(
        ...     pulp.PULP_CBC_CMD(msg=False)
        ... )

    Examine the solution.

        >>> for dv in pdispersion_from_cost_matrix.fac_vars:
        ...     if dv.varValue:
        ...         print(f"facility {dv.name} is selected")
        facility y_0_ is selected
        facility y_1_ is selected

  classmethod from_geodataframe(gdf_fac: GeoDataFrame, facility_col: str, p_facilities: int, predefined_facility_col: str = None, distance_metric: str = 'euclidean', name: str = 'p-dispersion')

    Create a `PDispersion` object based on a geodataframe. Calculate the cost matrix between facilities, and then use the from_cost_matrix method.

    Parameters:
      gdf_fac `geopandas.GeoDataFrame`

        Facility locations.

      facility_col `python:str`

        Facility candidate sites geometry column name.

      p_facilities `python:int`

        The number of facilities to be located.

      predefined_facility_col `python:str` (default `python:None`)

        Column name representing facilities are already defined. This a binary assignment per facility. For example, consider 3 facilites `['A', 'B', 'C']`. If facility `'B'` must be in the model solution, then the column should be `[0, 1, 0]`.

      distance_metric `python:str` (default ‘euclidean’)

        A metric used for the distance calculations supported by scipy.spatial.distance.cdist.

      name `python:str` (default ‘p-dispersion’)

        The name of the problem.

    Returns:
      `spopt.locate.p_dispersion.PDispersion`

    Examples

        >>> from spopt.locate.p_dispersion import PDispersion
        >>> from spopt.locate.util import simulated_geo_points
        >>> import geopandas
        >>> import pulp
        >>> import spaghetti

    Create a regular lattice.

        >>> lattice = spaghetti.regular_lattice((0, 0, 10, 10), 9, exterior=True)
        >>> ntw = spaghetti.Network(in_data=lattice)
        >>> streets = spaghetti.element_as_gdf(ntw, arcs=True)
        >>> streets_buffered = geopandas.GeoDataFrame(
        ...     geopandas.GeoSeries(streets["geometry"].buffer(0.2).unary_union),
        ...     crs=streets.crs,
        ...     columns=["geometry"]
        ... )

    Simulate points about the lattice.

        >>> facility_points = simulated_geo_points(streets_buffered, needed=5, seed=6)

    Snap the points to the network of lattice edges and extract as a `GeoDataFrame` object.

        >>> ntw.snapobservations(facility_points, "facilities", attribute=True)
        >>> facilities_snapped = spaghetti.element_as_gdf(
        ...     ntw, pp_name="facilities", snapped=True
        ... )

    Create and solve a `PDispersion` instance from the `GeoDataFrame` object.

        >>> pdispersion_from_geodataframe = PDispersion.from_geodataframe(
        ...     facilities_snapped,
        ...     "geometry",
        ...     p_fac=2,
        ...     distance_metric="euclidean"
        ... )
        >>> pdispersion_from_geodataframe = pdispersion_from_geodataframe.solve(
        ...     pulp.PULP_CBC_CMD(msg=False)
        ... )

    Examine the solution.

        >>> for dv in pdispersion_from_geodataframe.fac_vars:
        ...     if dv.varValue:
        ...         print(f"facility {dv.name} is selected")
        facility y_0_ is selected
        facility y_1_ is selected

  solve(solver: LpSolver)

    Solve the `PDispersion` model.

    Parameters:
      solver `pulp.LpSolver`

        A solver supported by `pulp`.

    Returns:
      `spopt.locate.p_dispersion.PDispersion`