Shelegia_Motta_2021.Models

   1import platform
   2import re
   3
   4# add typing support for Python 3.5 - 3.7
   5if re.match("3.[5-7].*", platform.python_version()) is None:
   6    from typing import Dict, List, Tuple, Literal, Final
   7else:
   8    from typing import Dict, List, Tuple
   9    from typing_extensions import Literal, Final
  10
  11import matplotlib.axes
  12import matplotlib.pyplot as plt
  13from numpy import arange, array
  14
  15import textwrap
  16plt.rcParams["font.family"] = "monospace"
  17
  18import Shelegia_Motta_2021
  19
  20
  21class BaseModel(Shelegia_Motta_2021.IModel):
  22    """
  23    The base model of the project consists of two players: The incumbent, which sells the primary product,
  24    and a start-up otherwise known as the entrant which sells a complementary product to the incumbent.
  25    One way to visualize a real-world application of this model would be to think of the entrant as a product or service
  26    that can be accessed through the platform of the incumbent, like a plug in that can be accessed through Google or a game on Facebook.
  27    The aim of this model is to monitor the choice that the entrant has between developing a substitute to or
  28    another compliment to the incumbent. The second aim is to observe the choice of the incumbent of whether
  29    to copy the original complementary product of the entrant by creating a perfect substitute or not.
  30    Seeing as the entrant may not have enough assets to fund a second product, the incumbent copying its first product
  31    would inhibit the entrant’s ability to fund its projects. This report will illustrate how the incumbent has a strategic incentive to copy
  32    the entrant if it is planning to compete and that it would refrain from copying if the entrant plans to develop a compliment.
  33    The subsequent models included in this report will introduce additional factors but will all be based on the basic model.
  34
  35    The equilibrium path arguably supports the “kill zone” argument: due to the risk of an exclusionary strategy by the incumbent,
  36    a potential entrant may prefer to avoid a market trajectory which would lead it to compete with the core product of a dominant incumbent
  37    and would choose to develop another complementary product instead.
  38    """
  39
  40    TOLERANCE: Final[float] = 10 ** (-10)
  41    """Tolerance for the comparison of two floating numbers."""
  42
  43    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
  44                 K: float = 0.2) -> None:
  45        """
  46        Initializes a valid BaseModel object.
  47
  48        The following preconditions have to be satisfied:
  49        - (A1b) $\delta$ / 2 < $\Delta$ < 3 * $\delta$ / 2
  50        - (A2) K < $\delta$ / 2
  51
  52        Parameters
  53        ----------
  54        u : float
  55            Utility gained from consuming the primary product.
  56        B : float
  57            Minimal difference between the return in case of a success and the return in case of failure of E. B is called the private benefit of the entrant in case of failure.
  58        small_delta : float
  59            ($\delta$) Additional utility gained from a complement combined with a primary product.
  60        delta : float
  61            ($\Delta$) Additional utility gained from the substitute of the entrant compared to the primary product of the incumbent.
  62        K : float
  63            Investment costs for the entrant to develop a second product.
  64        """
  65        super(BaseModel, self).__init__()
  66        assert small_delta / 2 < delta < 3 * small_delta / 2, "(A1b) not satisfied."
  67        assert K < small_delta / 2, "(A2) not satisfied."
  68        self._u: float = u
  69        self._B: float = B
  70        self._small_delta: float = small_delta
  71        self._delta: float = delta
  72        self._K: float = K
  73        self._copying_fixed_costs: Dict[str, float] = self._calculate_copying_fixed_costs_values()
  74        self._assets: Dict[str, float] = self._calculate_asset_values()
  75        self._payoffs: Dict[str, Dict[str, float]] = self._calculate_payoffs()
  76
  77    def _calculate_payoffs(self) -> Dict[str, Dict[str, float]]:
  78        """
  79        Calculates the payoffs for different market configurations with the formulas given in the paper.
  80
  81        The formulas are tabulated in BaseModel.get_payoffs.
  82
  83        Returns
  84        -------
  85        Dict[str, Dict[str, float]]
  86            Contains the mentioned payoffs for different market configurations.
  87        """
  88        return {'basic': {'pi(I)': self._u + self._small_delta / 2,
  89                          'pi(E)': self._small_delta / 2,
  90                          'CS': 0,
  91                          'W': self._u + self._small_delta
  92                          },
  93                'I(C)': {'pi(I)': self._u + self._small_delta,
  94                         'pi(E)': 0,
  95                         'CS': 0,
  96                         'W': self._u + self._small_delta
  97                         },
  98                'E(P)': {'pi(I)': 0,
  99                         'pi(E)': self._delta + self._small_delta,
 100                         'CS': self._u,
 101                         'W': self._u + self._delta + self._small_delta
 102                         },
 103                'I(C)E(P)': {'pi(I)': 0,
 104                             'pi(E)': self._delta,
 105                             'CS': self._u + self._small_delta,
 106                             'W': self._u + self._delta + self._small_delta
 107                             },
 108                'E(C)': {'pi(I)': self._u + self._small_delta,
 109                         'pi(E)': self._small_delta,
 110                         'CS': 0,
 111                         'W': self._u + 2 * self._small_delta
 112                         },
 113                'I(C)E(C)': {'pi(I)': self._u + 3 / 2 * self._small_delta,
 114                             'pi(E)': self._small_delta / 2,
 115                             'CS': 0,
 116                             'W': self._u + 2 * self._small_delta
 117                             }
 118                }
 119
 120    def _calculate_copying_fixed_costs_values(self) -> Dict[str, float]:
 121        """
 122        Calculates the thresholds for the fixed costs of copying for the incumbent.
 123
 124        The formulas are tabulated in BaseModel.get_copying_fixed_costs_values.
 125
 126        Returns
 127        -------
 128        Dict[str, float]
 129            Includes the thresholds for the fixed costs for copying of the incumbent.
 130        """
 131        return {'F(YY)s': self._small_delta / 2,
 132                'F(YN)s': self._u + self._small_delta * 3 / 2,
 133                'F(YY)c': self._small_delta,
 134                'F(YN)c': self._small_delta / 2}
 135
 136    def _calculate_asset_values(self) -> Dict[str, float]:
 137        """
 138        Calculates the thresholds for the assets of the entrant.
 139
 140        The formulas are tabulated in BaseModel.get_asset_values.
 141
 142        Returns
 143        -------
 144        Dict[str, float]
 145            Includes the thresholds for the assets of the entrant.
 146        """
 147        return {'A_s': self._B - (self._delta + 3 / 2 * self._small_delta - self._K),
 148                'A_c': self._B - (3 / 2 * self._small_delta - self._K),
 149                'A-s': self._B - (self._delta - self._K),
 150                'A-c': self._B - (1 / 2 * self._small_delta - self._K)}
 151
 152    def get_asset_values(self) -> Dict[str, float]:
 153        """
 154        Returns the asset thresholds of the entrant.
 155
 156        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
 157        |----------------|:----------|:-----------|
 158        | $\\underline{A}_S$ | A_s | $(2)\: B + K - \Delta - 3\delta/2$ |
 159        | $\\underline{A}_C$ | A_c | $(3)\: B + K - 3\delta/2$ |
 160        | $\overline{A}_S$ | A-s | $(4)\: B + K - \Delta$ |
 161        | $\overline{A}_C$ | A-c | $(5)\: B + K - \delta/2$ |
 162        <br>
 163        Returns
 164        -------
 165        Dict[str, float]
 166            Includes the thresholds for the assets of the entrant.
 167        """
 168        return self._assets
 169
 170    def get_copying_fixed_costs_values(self) -> Dict[str, float]:
 171        """
 172        Returns the fixed costs for copying thresholds of the incumbent.
 173
 174        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
 175        |----------|:-------|:--------|
 176        | $F^{YY}_S$ | F(YY)s | $(6)\: \delta/2$ |
 177        | $F^{YN}_S$ | F(YN)s | $(6)\: u + 3\delta/2$ |
 178        | $F^{YY}_C$ | F(YY)c | $(6)\: \delta$ |
 179        | $F^{YN}_C$ | F(YN)c | $(6)\: \delta/2$ |
 180        <br>
 181        Returns
 182        -------
 183        Dict[str, float]
 184            Includes the thresholds for the fixed costs for copying of the incumbent.
 185        """
 186        return self._copying_fixed_costs
 187
 188    def get_payoffs(self) -> Dict[str, Dict[str, float]]:
 189        """
 190        Returns the payoffs for different market configurations.
 191
 192        A market configuration can include:
 193        - $I_P$ : Primary product sold by the incumbent.
 194        - $I_C$ : Complementary product to $I_P$ potentially sold by the incumbent, which is copied from $E_C$.
 195        - $E_P$ : Perfect substitute to $I_P$ potentially sold by the entrant.
 196        - $E_C$ : Complementary product to $I_P$ currently sold by the entrant
 197        - $\\tilde{E}_C$ : Complementary product to $I_P$ potentially sold by the entrant.
 198        <br>
 199
 200        | Market Config. $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(I) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(E) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | CS $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | W $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
 201        |-----------------------|:--------|:--------|:--|:-|
 202        | $I_P$ ; $E_C$         | $u + \delta/2$ | $\delta/2$ | 0 | $u + \delta$ |
 203        | $I_P + I_C$ ; $E_C$   | $u + \delta$ | 0 | 0 | $u + \delta$ |
 204        | $I_P$ ; $E_P + E_C$   | 0 | $\Delta + \delta$ | $u$ | $u + \Delta + \delta$ |
 205        | $I_P + I_C$ ; $E_P + E_C$ | 0 | $\Delta$ | $u + \delta$ | $u + \Delta + \delta$ |
 206        | $I_P$ ; $E_C + \\tilde{E}_C$ | $u + \delta$ | $\delta$ | 0 | $u + 2\delta$ |
 207        | $I_P + I_C$ ; $E_C + \\tilde{E}_C$ | $u + 3\delta/2$ | $\delta/2$ | 0 | $u + 2\delta$ |
 208        <br>
 209
 210        Returns
 211        -------
 212        Dict[str, Dict[str, float]]
 213            Contains the mentioned payoffs for different market configurations.
 214        """
 215        return self._payoffs
 216
 217    def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
 218        result: Dict[str, str] = {"entrant": "", "incumbent": "", "development": ""}
 219        if self._copying_fixed_costs["F(YN)c"] <= F <= self._copying_fixed_costs["F(YN)s"] and A < self._assets["A-s"]:
 220            result.update({"entrant": self.ENTRANT_CHOICES["complement"]})
 221            result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
 222            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
 223        elif F <= self._copying_fixed_costs["F(YN)c"] and A < self._assets["A-s"]:
 224            result.update({"entrant": self.ENTRANT_CHOICES["indifferent"]})
 225            result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
 226            result.update({"development": self.DEVELOPMENT_OUTCOME["failure"]})
 227        else:
 228            result.update({"entrant": self.ENTRANT_CHOICES["substitute"]})
 229            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
 230            if F <= self._copying_fixed_costs["F(YY)s"]:
 231                result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
 232            else:
 233                result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
 234        return result
 235
 236    def _plot(self, coordinates: List[List[Tuple[float, float]]], labels: List[str],
 237              axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
 238        """
 239        Plots the areas containing the optimal choices and answers into a coordinate system.
 240
 241        Parameters
 242        ----------
 243        coordinates : List[List[Tuple[float, float]]]
 244            List of all polygons (list of coordinates) to plot.
 245        labels: List[str]
 246            List containing all the labels for the areas.
 247        axis : matplotlib.axes.Axes
 248            Axis to draw the plot on. (optional)
 249        **kwargs
 250            Optional key word arguments for the plots.<br>
 251            - title: title of the plot.<br>
 252            - xlabel: label for the x - axis.<br>
 253            - ylabel: label for the y - axis.<br>
 254            - options_legend: If true, an additional legend, explaining the options of the entrant and the incumbent, will be added to the plot.<br>
 255            - asset_legend: If true, an additional legend explaining the thresholds of the assets of the entrant will be added to the plot.<br>
 256            - costs_legend: If true, an additional legend explaining the thresholds of the fixed costs of copying for the incumbent will be added to the plot.<br>
 257            - legend_width : Maximum number of characters in one line in the legend (for adjustments to figure width).<br>
 258            - x_max : Maximum number plotted on the x - axis.<br>
 259            - y_max : Maximum number plotted on the y - axis.<br>
 260
 261        Returns
 262        -------
 263        Axis containing the plot.
 264        """
 265        if axis is None:
 266            plot_fig, axis = plt.subplots()
 267        self._draw_thresholds(axis, x_horizontal=kwargs.get("x_max", 0), y_vertical=kwargs.get("y_max", 0))
 268
 269        for i, coordinates in enumerate(coordinates):
 270            poly = plt.Polygon(coordinates, linewidth=0, color=self._get_color(i), label=labels[i])
 271            axis.add_patch(poly)
 272
 273        if kwargs.get("legend", True):
 274            axis.legend(bbox_to_anchor=(1.3, 1), loc="upper left")
 275            additional_legend: str = self._create_additional_legend(options_legend=kwargs.get('options_legend', False),
 276                                                                    assets_thresholds_legend=kwargs.get('asset_legend', False),
 277                                                                    costs_thresholds_legend=kwargs.get('costs_legend', False),
 278                                                                    width=kwargs.get('legend_width', 60))
 279            if additional_legend != "":
 280                axis.text(-0.1, -0.6, additional_legend, verticalalignment='top', linespacing=1, wrap=True)
 281
 282        BaseModel._set_axis_labels(axis, title=kwargs.get('title', ''),
 283                                   x_label=kwargs.get('xlabel', 'Assets of the entrant'),
 284                                   y_label=kwargs.get('ylabel', 'Fixed costs of copying for the incumbent'))
 285        BaseModel._set_axis(axis)
 286        return axis
 287
 288    def plot_incumbent_best_answers(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
 289        poly_coordinates: List[List[Tuple[float, float]]] = self._get_incumbent_best_answer_coordinates(
 290            kwargs.get("x_max", 0),
 291            kwargs.get("y_max", 0))
 292        poly_labels: List[str] = self._get_incumbent_best_answer_labels()
 293        kwargs.update({'title': kwargs.get('title', "Best Answers of the incumbent to the choices of the entrant")})
 294        return self._plot(coordinates=poly_coordinates, labels=poly_labels, axis=axis, **kwargs)
 295
 296    def _create_choice_answer_label(self, entrant: Literal["complement", "substitute", "indifferent"],
 297                                    incumbent: Literal["copy", "refrain"],
 298                                    development: Literal["success", "failure"],
 299                                    kill_zone: bool = False, acquisition: str = "") -> str:
 300        """
 301        Creates a label for the legend based on the choice of the entrant, the incumbent, the development outcome and additionally on possible acquisition.
 302
 303        Parameters
 304        ----------
 305        entrant: Literal["complement", "substitute", "indifferent"]
 306            choice of the entrant.
 307        incumbent: Literal["copy", "refrain"]
 308            choice of the incumbent.
 309        development: Literal["success", "failure"]
 310            outcome of the development.
 311        kill_zone: bool
 312            If true, the label adds a "(Kill Zone)" tag.
 313        acquisition: str
 314            The entity, which develops the additional product chosen by the entrant.
 315
 316        Returns
 317        -------
 318        str
 319            label based on the parameters mentioned above.
 320        """
 321        if acquisition != "":
 322            acquisition = "_" + acquisition
 323        return self.ENTRANT_CHOICES[entrant] + " $\\rightarrow$ " + self.INCUMBENT_CHOICES[
 324            incumbent] + " $\\rightarrow " + self.DEVELOPMENT_OUTCOME[development] + acquisition + "$" + (
 325                   "\n(Kill Zone)" if kill_zone else "")
 326
 327    def _get_incumbent_best_answer_labels(self) -> List[str]:
 328        """
 329        Returns a list containing the labels for the squares in the plot of the best answers of the incumbent to the choice of the entrant.
 330
 331        For the order of the labels refer to the file resources/dev_notes.md.
 332
 333        Returns
 334        -------
 335        List containing the labels for the squares in the plot of the best answers of the incumbent to the choice of the entrant.
 336        """
 337        return [
 338            # Area 1
 339            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="failure") + " \n" +
 340            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="failure"),
 341            # Area 2
 342            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success") + " \n" +
 343            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="failure"),
 344            # Area 3
 345            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success") + " \n" +
 346            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="success"),
 347            # Area 4
 348            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="failure") + " \n" +
 349            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success"),
 350            # Area 5
 351            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success") + " \n" +
 352            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="success"),
 353            # Area 6
 354            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success") + " \n" +
 355            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success"),
 356        ]
 357
 358    def _get_incumbent_best_answer_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
 359        """
 360        Returns a list containing the coordinates for the areas in the plot of the best answers of the incumbent to the choice of the entrant.
 361
 362        For the order of the areas refer to the file resources/dev_notes.md.
 363
 364        Returns
 365        -------
 366        List[List[Tuple[float, float]]]
 367            List containing the coordinates for the areas in the plot of the best answers of the incumbent to the choice of the entrant.
 368        """
 369        y_max = self._get_y_max(y_max)
 370        x_max = self._get_x_max(x_max)
 371        return [
 372            # Area 1
 373            [(0, 0), (self._assets['A-s'], 0), (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0)),
 374             (0, max(self._copying_fixed_costs['F(YN)c'], 0))],
 375            # Area 2
 376            [(self._assets['A-s'], 0), (self._assets['A-c'], 0),
 377             (self._assets['A-c'], self._copying_fixed_costs['F(YY)s']),
 378             (self._assets['A-s'], self._copying_fixed_costs['F(YY)s'])],
 379            # Area 3
 380            [(self._assets['A-c'], 0), (x_max, 0), (x_max, self._copying_fixed_costs['F(YY)s']),
 381             (self._assets['A-c'], self._copying_fixed_costs['F(YY)s'])],
 382            # Area 4
 383            [(0, max(self._copying_fixed_costs['F(YN)c'], 0)),
 384             (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0)),
 385             (self._assets['A-s'], self._copying_fixed_costs['F(YN)s']), (0, self._copying_fixed_costs['F(YN)s'])],
 386            # Area 5
 387            [(self._assets['A-c'], self._copying_fixed_costs['F(YY)s']), (x_max, self._copying_fixed_costs['F(YY)s']),
 388             (x_max, self._copying_fixed_costs['F(YY)c']), (self._assets['A-c'], self._copying_fixed_costs['F(YY)c'])],
 389            # Area 6
 390            [(self._assets['A-s'], self._copying_fixed_costs['F(YY)s']),
 391             (self._assets['A-c'], self._copying_fixed_costs['F(YY)s']),
 392             (self._assets['A-c'], self._copying_fixed_costs['F(YY)c']), (x_max, self._copying_fixed_costs['F(YY)c']),
 393             (x_max, y_max), (0, y_max),
 394             (0, self._copying_fixed_costs['F(YN)s']), (self._assets['A-s'], self._copying_fixed_costs['F(YN)s'])]]
 395
 396    def plot_equilibrium(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
 397        poly_coordinates: List[List[Tuple[float, float]]] = self._get_equilibrium_coordinates(kwargs.get("x_max", 0),
 398                                                                                              kwargs.get("y_max", 0))
 399        poly_labels: List[str] = self._get_equilibrium_labels()
 400        kwargs.update({'title': kwargs.get('title', 'Equilibrium Path')})
 401        return self._plot(coordinates=poly_coordinates, labels=poly_labels, axis=axis, **kwargs)
 402
 403    def _get_equilibrium_labels(self) -> List[str]:
 404        """
 405        Returns a list containing the labels for the squares in the plot of the equilibrium path.
 406
 407        For the order of the squares refer to the file resources/dev_notes.md.
 408
 409        Returns
 410        -------
 411        List[str]
 412            List containing the labels for the squares in the plot of the best answers of the equilibrium path.
 413        """
 414        return [
 415            # Area 1
 416            self._create_choice_answer_label(entrant="indifferent", incumbent="copy", development="failure"),
 417            # Area 2
 418            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success"),
 419            # Area 3
 420            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
 421                                             kill_zone=True),
 422            # Area 4
 423            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success")
 424        ]
 425
 426    def _get_equilibrium_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
 427        """
 428        Returns a list containing the coordinates for the areas in the plot of the equilibrium path.
 429
 430        For the order of the areas refer to the file resources/dev_notes.md.
 431
 432        Returns
 433        -------
 434        List[List[Tuple[float, float]]]
 435            List containing the coordinates for the areas in the plot of the best answers of the equilibrium path.
 436        """
 437        y_max = self._get_y_max(y_max)
 438        x_max = self._get_x_max(x_max)
 439        return [
 440            # Area 1
 441            [(0, 0), (self._assets['A-s'], 0), (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0)),
 442             (0, max(self._copying_fixed_costs['F(YN)c'], 0))],
 443            # Area 2
 444            [(self._assets['A-s'], 0), (x_max, 0), (x_max, self._copying_fixed_costs['F(YY)s']),
 445             (self._assets['A-s'], self._copying_fixed_costs['F(YY)s'])],
 446            # Area 3
 447            [(0, max(self._copying_fixed_costs['F(YN)c'], 0)),
 448             (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0)),
 449             (self._assets['A-s'], self._copying_fixed_costs['F(YN)s']), (0, self._copying_fixed_costs['F(YN)s'])],
 450            # Area 4
 451            [(self._assets['A-s'], self._copying_fixed_costs['F(YY)s']), (x_max, self._copying_fixed_costs['F(YY)s']),
 452             (x_max, y_max), (0, y_max), (0, self._copying_fixed_costs['F(YN)s']),
 453             (self._assets['A-s'], self._copying_fixed_costs['F(YN)s'])]]
 454
 455    def plot_payoffs(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
 456        if axis is None:
 457            plot_fig, axis = plt.subplots()
 458        index = arange(0, len(self._payoffs) * 2, 2)
 459        bar_width = 0.35
 460        spacing = 0.05
 461
 462        self._plot_payoffs_bars(axis, bar_width, index, spacing, **kwargs)
 463
 464        axis.set_xlabel('Market Configurations')
 465        axis.set_title('Payoffs for different Market Configurations')
 466        self._set_payoffs_ticks(axis, bar_width, index, spacing)
 467        if kwargs.get("legend", True):
 468            self._set_payoff_legend(axis, kwargs.get("products_legend", False))
 469        self._set_payoffs_figure(axis)
 470        return axis
 471
 472    def _plot_payoffs_bars(self, axis: matplotlib.axes.Axes, bar_width: float, index: array, spacing: float,
 473                           **kwargs) -> None:
 474        """
 475        Plots the bars representing the payoffs for different market configurations of different stakeholders on the specified axis.
 476
 477        Parameters
 478        ----------
 479        axis matplotlib.axes.Axes
 480            To plot the bars on.
 481        bar_width: float
 482            Width of a bar in the plot.
 483        index: np.array
 484            Index of the different market configurations in the plot.
 485        spacing: float
 486            Spacing between the bars on the plot.
 487        **kwargs
 488            Optional key word arguments for the payoff plot.<br>
 489            - opacity : Opacity of the not optimal payoffs.<br>
 490        """
 491        for counter, utility_type in enumerate(self._payoffs[list(self._payoffs.keys())[0]].keys()):
 492            utility_values: List[float] = []
 493            for market_configuration in self._payoffs:
 494                utility_values.append(self._payoffs[market_configuration][utility_type])
 495
 496            bars = axis.bar(index + counter * (bar_width + spacing), utility_values, bar_width,
 497                            alpha=kwargs.get("opacity", 0.2),
 498                            color=self._get_color(counter),
 499                            edgecolor=None,
 500                            label=self._convert_payoffs_label(utility_type))
 501            max_indices: List[int] = list(
 502                filter(lambda x: utility_values[x] == max(utility_values), range(len(utility_values))))
 503            for max_index in max_indices:
 504                bars[max_index].set_alpha(1)
 505
 506    def _set_payoff_legend(self, axis: matplotlib.axes.Axes, products_legend: bool = False) -> None:
 507        """
 508        Creates the legend and an additional legend for the products of the entrant and the incumbent,
 509
 510        Parameters
 511        ----------
 512        axis: matplotlib.axes.Axes
 513            To set the legends for.
 514        products_legend: bool
 515            If true, an additional legend, containing all possible products of the entrant and the incumbent, will be created.
 516        """
 517        axis.legend(bbox_to_anchor=(1.02, 1), loc='upper left', ncol=1)
 518        if products_legend:
 519            axis.text(-0.7, -0.8, self._get_market_configuration_annotations(), verticalalignment="top")
 520
 521    def _set_payoffs_ticks(self, axis: matplotlib.axes.Axes, bar_width: float, index: array, spacing: float) -> None:
 522        """
 523        Sets the x - and y - ticks for the plot of the payoffs for different market configurations.
 524
 525        Parameters
 526        ----------
 527        axis matplotlib.axes.Axes
 528            To adjust the ticks on.
 529        bar_width: float
 530            Width of a bar in the plot.
 531        index: np.array
 532            Index of the different market configurations in the plot.
 533        spacing: float
 534            Spacing between the bars on the plot.
 535        """
 536        axis.set(yticklabels=[])
 537        axis.tick_params(left=False)
 538        axis.set_xticks(index + 1.5 * (bar_width + spacing))
 539        axis.set_xticklabels(tuple([self._convert_market_configuration_label(i) for i in self._payoffs.keys()]))
 540
 541    @staticmethod
 542    def _set_payoffs_figure(axis: matplotlib.axes.Axes) -> None:
 543        """
 544        Adjust the matplotlib figure to plot the payoffs for different market configurations.
 545
 546        Parameters
 547        ----------
 548        axis: matplotlib.axes.Axes
 549            To adjust for the payoff plot.
 550        """
 551        axis.figure.set_size_inches(10, 5)
 552        axis.figure.tight_layout()
 553
 554    @staticmethod
 555    def _get_market_configuration_annotations() -> str:
 556        """
 557        Returns a string containing all product options for the entrant and the incumbent.
 558
 559        Returns
 560        -------
 561        str
 562            Contains all product options for the entrant and the incumbent.
 563        """
 564        return "$I_P$: Primary product sold by the incumbent\n" \
 565               "$I_C$: Copied complementary product to $I_P$ potentially sold by the incumbent\n" \
 566               "$E_P$: Perfect substitute to $I_P$ potentially sold by the entrant\n" \
 567               "$E_C$: Complementary product to $I_P$ currently sold by the entrant\n" \
 568               "$\\tilde{E}_C$: Complementary product to $I_P$ potentially sold by the entrant\n" \
 569               "\nThe bars representing the maximum payoff for a stakeholder are fully filled."
 570
 571    @staticmethod
 572    def _convert_payoffs_label(raw_label: str) -> str:
 573        """
 574        Converts keys of the payoffs dict to latex labels.
 575
 576        Parameters
 577        ----------
 578        raw_label: str
 579            As given as key in the payoffs dict.
 580
 581        Returns
 582        -------
 583        str
 584            Latex compatible pretty label.
 585        """
 586        label: str = raw_label.replace("pi", "$\pi$")
 587        label = label.replace("CS", "Consumer Surplus")
 588        label = label.replace("W", "Welfare")
 589        return label
 590
 591    @staticmethod
 592    def _convert_market_configuration_label(raw_label: str) -> str:
 593        """
 594        Returns the latex string for a specific market configuration.
 595
 596        Parameters
 597        ----------
 598        raw_label
 599            Of the market configuration as given as key in the payoffs dict.
 600
 601        Returns
 602        -------
 603        str
 604            Corresponding latex label for the market configuration as given as key in the payoffs dict.
 605        """
 606        labels: Dict[str] = {"basic": "$I_P;E_C$",
 607                             "I(C)": "$I_P+I_C;E_C$",
 608                             "E(P)": "$I_P;E_C+E_P$",
 609                             "I(C)E(P)": "$I_P+I_C;E_C+E_P$",
 610                             "E(C)": "$I_P;E_C+\\tilde{E}_C$",
 611                             "I(C)E(C)": "$I_P+I_C;E_C+\\tilde{E}_C$"}
 612        return labels.get(raw_label, 'No valid market configuration')
 613
 614    def _get_x_max(self, x_max: float = 0) -> float:
 615        """
 616        Returns the maximum value to plot on the x - axis.
 617
 618        Parameters
 619        ----------
 620        x_max: float
 621            Preferred value for the maximum value on the x - axis.
 622
 623        Returns
 624        -------
 625        float
 626            Maximum value (which is feasible) to plot on the x - axis.
 627        """
 628        auto_x_max: float = round(self._assets['A-c'] * 1.3, 1)
 629        return x_max if x_max > self._assets['A-c'] else auto_x_max
 630
 631    def _get_y_max(self, y_max: float = 0) -> float:
 632        """
 633        Returns the maximum value to plot on the y - axis.
 634
 635        Parameters
 636        ----------
 637        y_max: float
 638            Preferred value for the maximum value on the y - axis.
 639
 640        Returns
 641        -------
 642        float
 643            Maximum value (which is feasible) to plot on the y - axis.
 644        """
 645        auto_y_max: float = round(self._copying_fixed_costs['F(YN)s'] * 1.3, 1)
 646        return y_max if y_max > self._copying_fixed_costs['F(YN)s'] else auto_y_max
 647
 648    def _draw_thresholds(self, axis: matplotlib.axes.Axes, x_horizontal: float = 0, y_vertical: float = 0) -> None:
 649        """
 650        Draws the thresholds and the corresponding labels on a given axis.
 651
 652        Parameters
 653        ----------
 654        axis: matplotlib.axes.Axes
 655            Axis to draw the thresholds on.
 656        x_horizontal : float
 657            X - coordinate for horizontal thresholds labels (fixed costs of copying).
 658        y_vertical : float
 659            Y - coordinate for vertical thresholds labels (assets of the entrant).
 660        """
 661        # horizontal lines (fixed cost of copying thresholds)
 662        self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YN)s'], label="$F^{YN}_S$",
 663                                              x=x_horizontal)
 664        self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YY)c'], label="$F^{YY}_C$",
 665                                              x=x_horizontal)
 666        if abs(self._copying_fixed_costs['F(YY)s'] - self._copying_fixed_costs['F(YN)c']) < BaseModel.TOLERANCE:
 667            self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YY)s'],
 668                                                  label="$F^{YY}_S=F^{YN}_C$", x=x_horizontal)
 669        else:
 670            self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YY)s'], label="$F^{YY}_S$",
 671                                                  x=x_horizontal)
 672            if self._copying_fixed_costs['F(YN)c'] >= 0:
 673                self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YN)c'], label="$F^{YN}_C$",
 674                                                      x=x_horizontal)
 675        # vertical lines (asset thresholds)
 676        self._draw_vertical_line_with_label(axis, x=self._assets['A-s'], label=r'$\bar{A}_S$', y=y_vertical)
 677        self._draw_vertical_line_with_label(axis, x=self._assets['A-c'], label=r'$\bar{A}_C$', y=y_vertical)
 678
 679    def _draw_horizontal_line_with_label(self, axis: matplotlib.axes.Axes, y: float, **kwargs) -> None:
 680        """
 681        Draws a horizontal line at a given y - coordinate and writes the corresponding label at the edge.
 682
 683        Parameters
 684        ----------
 685        axis
 686            To draw the horizontal line and label on.
 687        y
 688            Coordinate of the of the line on the y - axis.
 689        **kwargs
 690            Optional key word arguments for the equilibrium plot.<br>
 691            - label: Label for the horizontal line written at the edge.<br>
 692            - x: X - coordinate for horizontal thresholds labels (fixed costs of copying).<br>
 693        """
 694        label_x: float = self._get_x_max(kwargs.get("x", 0)) + 0.05
 695        axis.axhline(y, linestyle='--', color='k')
 696        axis.text(label_x, y, kwargs.get("label", ""))
 697
 698    def _draw_vertical_line_with_label(self, axis: matplotlib.axes.Axes, x: float, **kwargs) -> None:
 699        """
 700        Draws a vertical line at a given x - coordinate and writes the corresponding label at the edge.
 701
 702        Parameters
 703        ----------
 704        axis
 705            To draw the vertical line and label on.
 706        x
 707            Coordinate of the of the line on the x - axis.
 708        **kwargs
 709            Optional key word arguments for the equilibrium plot.<br>
 710            - label: Label for the horizontal line written at the edge.<br>
 711            - y: Y - coordinate for vertical thresholds labels (assets of the entrant).<br>
 712        """
 713        label_y: float = self._get_y_max(kwargs.get("y", 0)) + 0.15
 714        axis.axvline(x, linestyle='--', color='k')
 715        axis.text(x, label_y, kwargs.get("label", ""))
 716
 717    def _create_additional_legend(self, options_legend: bool, assets_thresholds_legend: bool, costs_thresholds_legend: bool, width: int) -> str:
 718        """
 719        Handles the creation of the additional legend for the options of the entrant and incumbent as well as the legend for the thresholds.
 720
 721        Parameters
 722        ----------
 723        options_legend: bool
 724            States all options of the entrant and the incumbent.
 725        assets_thresholds_legend
 726            States the thresholds for the assets of the entrant used in the plots.
 727        costs_thresholds_legend
 728            States the thresholds for the fixed costs of copying of the incumbent used in the plots.
 729
 730        Returns
 731        -------
 732        str
 733            Containing the legend for the options of the entrant and the incumbent as well as the legend for the thresholds.
 734        """
 735        legend: str = ""
 736        if options_legend:
 737            legend += self._create_options_legend(width=width)
 738        if assets_thresholds_legend:
 739            legend += "\n\n" if options_legend else ""
 740            legend += self._create_asset_thresholds_legend(width=width)
 741        if costs_thresholds_legend:
 742            legend += "\n\n" if options_legend or assets_thresholds_legend else ""
 743            legend += self._create_cost_thresholds_legend(width=width)
 744        return legend
 745
 746    def _create_options_legend(self, width: int) -> str:
 747        """
 748        Creates a legend for the options of the entrant and the incumbent.
 749
 750        Returns
 751        -------
 752        str
 753            Containing the legend for the options of the entrant and the incumbent.
 754        """
 755        return "Options of the entrant:\n" + \
 756               self._format_legend_line(self.ENTRANT_CHOICES['complement'] + ": Develop an additional complementary product to a primary product.", width=width) + "\n" + \
 757               self._format_legend_line(self.ENTRANT_CHOICES['substitute'] + ": Develop an substitute to the primary product of the incumbent.", width=width) + "\n" + \
 758               self._format_legend_line(self.ENTRANT_CHOICES['indifferent'] + " : Indifferent between the options mentioned above.", width=width) + "\n" + \
 759               "\nOptions of the incumbent:\n" + \
 760               self._format_legend_line(self.INCUMBENT_CHOICES['copy'] + " : Copy the original complement of the entrant.", width=width) + "\n" + \
 761               self._format_legend_line(self.INCUMBENT_CHOICES['refrain'] + " : Do not copy the original complement of the entrant.", width=width) + "\n" + \
 762               "\nOutcomes of the development:\n" + \
 763               self._format_legend_line(self.DEVELOPMENT_OUTCOME['success'] + " : The entrant has sufficient assets to develop the product.", width=width) + "\n" + \
 764               self._format_legend_line(self.DEVELOPMENT_OUTCOME['failure'] + " : The entrant has not sufficient assets to develop the product.", width=width)
 765
 766    @staticmethod
 767    def _create_asset_thresholds_legend(width: int) -> str:
 768        """
 769        Creates a legend for the asset of the entrant thresholds used in the plots. The legend is compatible with latex.
 770
 771        Returns
 772        -------
 773        str
 774             Containing the legend for the thresholds used in the plots.
 775        """
 776        return "Thresholds for the assets of the entrant:\n" + \
 777               BaseModel._format_legend_line(r'$\bar{A}_S$' + ": Minimum level of assets to ensure a perfect substitute gets funded if the incumbent copies.", width=width) + "\n" + \
 778               BaseModel._format_legend_line(r'$\bar{A}_S$' + ": Minimum level of assets to ensure a perfect substitute gets funded if the incumbent copies.", width=width) + "\n" + \
 779               BaseModel._format_legend_line(r'$\bar{A}_C$' + ": Minimum level of assets to ensure another complement gets funded if the incumbent copies.", width=width) + "\n" + \
 780               BaseModel._format_legend_line("If the incumbent does not copy, the entrant will have sufficient assets.", width=width)
 781
 782    @staticmethod
 783    def _create_cost_thresholds_legend(width: int) -> str:
 784        """
 785        Creates a legend for the thresholds used in the plots. The legend is compatible with latex.
 786
 787        Returns
 788        -------
 789        str
 790             Containing the legend for the thresholds used in the plots.
 791        """
 792        return "Thresholds for the fixed costs of copying for the incumbent:\n" + \
 793               BaseModel._format_legend_line(r'$F^{YY}_S$' + ": Maximum costs of copying that ensure that the incumbent copies the entrant if the entrant is guaranteed to invest in a perfect substitute.", width=width) + "\n" + \
 794               BaseModel._format_legend_line(r'$F^{YN}_S$' + ": Maximum costs of copying that ensure that the incumbent copies the entrant if the copying prevents the entrant from developing a perfect substitute.", width=width) + "\n" + \
 795               BaseModel._format_legend_line(r'$F^{YY}_C$' + ": Maximum costs of copying that ensure that the incumbent copies the entrant if the entrant is guaranteed to invest in another complement.", width=width) + "\n" + \
 796               BaseModel._format_legend_line(r'$F^{YN}_C$' + ": Maximum costs of copying that ensure that the incumbent copies the entrant if the copying prevents the entrant from developing another complement.", width=width)
 797
 798    @staticmethod
 799    def _format_legend_line(line: str, width: int = 60, latex: bool = True) -> str:
 800        space: str = "$\quad$" if latex else " " * 4
 801        return textwrap.fill(line, width=width, initial_indent='', subsequent_indent=space * 3)
 802
 803    @staticmethod
 804    def _get_color(i: int) -> str:
 805        """
 806        Returns a string corresponding to a matplotlib - color for a given index.
 807
 808        The index helps to get different colors for different items, when iterating over list/dict/etc..
 809
 810        Parameters
 811        ----------
 812        i: int
 813            Index of the color.
 814        Returns
 815        -------
 816        str
 817            A string corresponding to a matplotlib - color for a given index.
 818        """
 819        return ['salmon', 'khaki', 'limegreen', 'turquoise', 'powderblue', 'thistle', 'pink'][i]
 820
 821    @staticmethod
 822    def _set_axis(axis: matplotlib.axes.Axes) -> None:
 823        """
 824        Adjusts the axis to the given viewport.
 825
 826        Parameters
 827        ----------
 828        axis: matplotlib.axes.Axes
 829            To adjust to the given viewport.
 830        """
 831        axis.autoscale_view()
 832        axis.figure.tight_layout()
 833
 834    @staticmethod
 835    def _set_axis_labels(axis: matplotlib.axes.Axes, title: str = "", x_label: str = "", y_label: str = "") -> None:
 836        """
 837        Sets all the labels for a plot, containing the title, x - label and y - label.
 838
 839        Parameters
 840        ----------
 841        axis
 842            Axis to set the labels for.
 843        title
 844            Title of the axis.
 845        x_label
 846            Label of the x - axis.
 847        y_label
 848            Label of the y - axis.
 849        """
 850        axis.set_title(title, loc='left', y=1.1)
 851        axis.set_xlabel(x_label)
 852        axis.set_ylabel(y_label)
 853
 854    def __str__(self) -> str:
 855        str_representation = self._create_asset_str()
 856
 857        str_representation += "\n" + self._create_copying_costs_str()
 858
 859        str_representation += "\n" + self._create_payoff_str()
 860
 861        return str_representation
 862
 863    def _create_payoff_str(self):
 864        """
 865        Creates a string representation for the payoffs of different stakeholder for different market configurations.
 866
 867        See Shelegia_Motta_2021.IModel.get_payoffs for the formulas of the payoffs.
 868
 869        Returns
 870        -------
 871        str
 872            String representation for the payoffs of different stakeholder for different market configurations
 873        """
 874        market_configurations: List[str] = list(self._payoffs.keys())
 875        str_representation = 'Payoffs for different Market Configurations:\n\t' + ''.join(
 876            ['{0: <14}'.format(item) for item in market_configurations])
 877        for utility_type in self._payoffs[market_configurations[0]].keys():
 878            str_representation += '\n\t'
 879            for market_configuration in market_configurations:
 880                str_representation += '-' + '{0: <4}'.format(utility_type).replace('pi', 'π') + ': ' + '{0: <5}'.format(
 881                    str(self._payoffs[market_configuration][utility_type])) + '| '
 882        return str_representation
 883
 884    def _create_copying_costs_str(self):
 885        """
 886        Creates a string representation for the fixed costs of copying for the incumbent.
 887
 888        See Shelegia_Motta_2021.IModel.get_copying_fixed_costs_values for the formulas of the fixed costs of copying.
 889
 890        Returns
 891        -------
 892        str
 893            String representation for the fixed costs of copying for the incumbent.
 894        """
 895        str_representation = 'Costs for copying:'
 896        for key in self._copying_fixed_costs.keys():
 897            str_representation += '\n\t- ' + key + ':\t' + str(self._copying_fixed_costs[key])
 898        return str_representation
 899
 900    def _create_asset_str(self):
 901        """
 902        Creates a string representation for the assets of the entrant.
 903
 904        See Shelegia_Motta_2021.IModel.get_asset_values for the formulas of the assets of the entrant.
 905
 906        Returns
 907        -------
 908        str
 909            String representation for the assets of the entrant.
 910        """
 911        str_representation: str = 'Assets:'
 912        for key in self._assets:
 913            str_representation += '\n\t- ' + key + ':\t' + str(self._assets[key])
 914        return str_representation
 915
 916    def __call__(self, A: float, F: float) -> Dict[str, str]:
 917        """
 918        Makes the object callable and will return the equilibrium for a given pair of copying fixed costs of the incumbent
 919        and assets of the entrant.
 920
 921        See Shelegia_Motta_2021.IModel.get_optimal_choice for further documentation.
 922        """
 923        return self.get_optimal_choice(A=A, F=F)
 924
 925
 926class BargainingPowerModel(BaseModel):
 927    """
 928    Besides the parameters used in the paper (and in the BaseModel), this class will introduce the parameter $\\beta$ in the models, called
 929    the bargaining power of the incumbent. $\\beta$ describes how much of the profits from the complementary product of the entrant will go to the incumbent
 930    In the paper the default value $\\beta=0.5$ is used to derive the results, which indicate an equal share of the profits.
 931    """
 932
 933    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
 934                 K: float = 0.2, beta: float = 0.5):
 935        """
 936        Besides $\\beta$ the parameters in this model do not change compared to Shelegia_Motta_2021.Models.BaseModel.
 937
 938        Parameters
 939        ----------
 940        beta: float
 941            Bargaining power of the incumbent relative to the entrant ($0 < \\beta < 1$).
 942        """
 943        assert 0 < beta < 1, 'Invalid bargaining power beta (has to be between 0 and 1).'
 944        self._beta: float = beta
 945        super(BargainingPowerModel, self).__init__(u=u, B=B, small_delta=small_delta, delta=delta, K=K)
 946
 947    def _calculate_payoffs(self) -> Dict[str, Dict[str, float]]:
 948        """
 949        Calculates the payoffs for different market configurations with the formulas given in the paper.
 950
 951        The formulas are tabulated in BargainingPowerModel.get_payoffs, which are different to the BaseModel.
 952
 953        Returns
 954        -------
 955        Dict[str, Dict[str, float]]
 956            Contains the mentioned payoffs for different market configurations.
 957        """
 958        payoffs: Dict[str, Dict[str, float]] = super()._calculate_payoffs()
 959        # basic market.
 960        payoffs['basic']['pi(I)'] = self._u + self._small_delta * self._beta
 961        payoffs['basic']['pi(E)'] = self._small_delta * (1 - self._beta)
 962
 963        # additional complement of the entrant
 964        payoffs['E(C)']['pi(I)'] = self._u + 2 * self._small_delta * self._beta
 965        payoffs['E(C)']['pi(E)'] = 2 * self._small_delta * (1 - self._beta)
 966
 967        # additional complement of the incumbent and the entrant
 968        payoffs['I(C)E(C)']['pi(I)'] = self._u + self._small_delta * (1 + self._beta)
 969        payoffs['I(C)E(C)']['pi(E)'] = self._small_delta * (1 - self._beta)
 970
 971        return payoffs
 972
 973    def _calculate_copying_fixed_costs_values(self) -> Dict[str, float]:
 974        """
 975        Calculates the thresholds for the fixed costs of copying for the incumbent.
 976
 977        The formulas are tabulated in BargainingPowerModel.get_copying_fixed_costs_values, which are different to the BaseModel.
 978
 979        Returns
 980        -------
 981        Dict[str, float]
 982            Includes the thresholds for the fixed costs for copying of the incumbent.
 983        """
 984        return {'F(YY)s': self._small_delta * (1 - self._beta),
 985                'F(YN)s': self._u + self._small_delta * (2 - self._beta),
 986                'F(YY)c': 2 * self._small_delta * (1 - self._beta),
 987                'F(YN)c': self._small_delta * (2 - 3 * self._beta)}
 988
 989    def _calculate_asset_values(self) -> Dict[str, float]:
 990        """
 991        Calculates the thresholds for the assets of the entrant.
 992
 993        The formulas are tabulated in BargainingPowerModel.get_asset_values, which are different to the BaseModel.
 994
 995        Returns
 996        -------
 997        Dict[str, float]
 998            Includes the thresholds for the assets of the entrant.
 999        """
1000        return {'A_s': self._K + self._B - self._delta - self._small_delta * (2 - self._beta),
1001                'A_c': self._K + self._B - 3 * self._small_delta * (1 - self._beta),
1002                'A-s': self._K + self._B - self._delta,
1003                'A-c': self._K + self._B - self._small_delta * (1 - self._beta)}
1004
1005    def get_asset_values(self) -> Dict[str, float]:
1006        """
1007        Returns the asset thresholds of the entrant.
1008
1009        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1010        |----------------|:----------|:-----------|
1011        | $\\underline{A}_S$ | A_s | $B + K - \Delta - \delta(2 - \\beta)$ |
1012        | $\\underline{A}_C$ | A_c | $B + K - 3\delta(1 - \\beta)$ |
1013        | $\overline{A}_S$ | A-s | $B + K - \Delta$ |
1014        | $\overline{A}_C$ | A-c | $B + K - \delta(1 - \\beta)$ |
1015        <br>
1016        Returns
1017        -------
1018        Dict[str, float]
1019            Includes the thresholds for the assets of the entrant.
1020        """
1021        return self._assets
1022
1023    def get_copying_fixed_costs_values(self) -> Dict[str, float]:
1024        """
1025        Returns the fixed costs for copying thresholds of the incumbent.
1026
1027        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1028        |----------|:-------|:--------|
1029        | $F^{YY}_S$ | F(YY)s | $\delta(1 - \\beta)$ |
1030        | $F^{YN}_S$ | F(YN)s | $u + \delta(2 - \\beta)$ |
1031        | $F^{YY}_C$ | F(YY)c | $2\delta(1 - \\beta)$ |
1032        | $F^{YN}_C$ | F(YN)c | $\delta(2 - \\beta)$ |
1033        <br>
1034        Returns
1035        -------
1036        Dict[str, float]
1037            Includes the thresholds for the fixed costs for copying of the incumbent.
1038        """
1039        return self._copying_fixed_costs
1040
1041    def get_payoffs(self) -> Dict[str, Dict[str, float]]:
1042        """
1043        Returns the payoffs for different market configurations.
1044
1045        A market configuration can include:
1046        - $I_P$ : Primary product sold by the incumbent.
1047        - $I_C$ : Complementary product to $I_P$ potentially sold by the incumbent, which is copied from $E_C$.
1048        - $E_P$ : Perfect substitute to $I_P$ potentially sold by the entrant.
1049        - $E_C$ : Complementary product to $I_P$ currently sold by the entrant
1050        - $\\tilde{E}_C$ : Complementary product to $I_P$ potentially sold by the entrant.
1051        <br>
1052
1053        | Market Config. $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(I) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(E) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | CS $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | W $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1054        |-----------------------|:--------|:--------|:--|:-|
1055        | $I_P$ ; $E_C$         | $u + \delta\\beta$ | $\delta(1 - \\beta)$ | 0 | $u + \delta$ |
1056        | $I_P + I_C$ ; $E_C$   | $u + \delta$ | 0 | 0 | $u + \delta$ |
1057        | $I_P$ ; $E_P + E_C$   | 0 | $\Delta + \delta$ | $u$ | $u + \Delta + \delta$ |
1058        | $I_P + I_C$ ; $E_P + E_C$ | 0 | $\Delta$ | $u + \delta$ | $u + \Delta + \delta$ |
1059        | $I_P$ ; $E_C + \\tilde{E}_C$ | $u + 2\delta\\beta$ | $2\delta(1 - \\beta)$ | 0 | $u + 2\delta$ |
1060        | $I_P + I_C$ ; $E_C + \\tilde{E}_C$ | $u + \delta(1 + \\beta)$ | $\delta(1 - \\beta)$ | 0 | $u + 2\delta$ |
1061        <br>
1062
1063        Returns
1064        -------
1065        Dict[str, Dict[str, float]]
1066            Contains the mentioned payoffs for different market configurations.
1067        """
1068        return self._payoffs
1069
1070    def _get_incumbent_best_answer_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
1071        coordinates: List[List[Tuple[float, float]]] = super(BargainingPowerModel,
1072                                                             self)._get_incumbent_best_answer_coordinates(x_max=x_max,
1073                                                                                                          y_max=y_max)
1074        # add additional area 7
1075        if self._copying_fixed_costs["F(YY)s"] != self._copying_fixed_costs["F(YN)c"]:
1076            coordinates.append([(self._assets['A-s'], self._copying_fixed_costs['F(YY)s']),
1077                                (self._assets['A-c'], self._copying_fixed_costs['F(YY)s']),
1078                                (self._assets['A-c'], max(self._copying_fixed_costs['F(YN)c'], 0)),
1079                                (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0))])
1080        return coordinates
1081
1082    def _get_incumbent_best_answer_labels(self) -> List[str]:
1083        labels: List[str] = super(BargainingPowerModel, self)._get_incumbent_best_answer_labels()
1084        # add additional label for area 7
1085        if self._copying_fixed_costs["F(YY)s"] != self._copying_fixed_costs["F(YN)c"]:
1086            if self._copying_fixed_costs["F(YY)s"] > self._copying_fixed_costs["F(YN)c"]:
1087                labels.append(
1088                    # Area 7
1089                    self._create_choice_answer_label(entrant="substitute", incumbent="copy",
1090                                                     development="success") + " \n" +
1091                    self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success"),
1092                )
1093            else:
1094                labels.append(
1095                    # Area 7
1096                    self._create_choice_answer_label(entrant="substitute", incumbent="refrain",
1097                                                     development="success") + " \n" +
1098                    self._create_choice_answer_label(entrant="complement", incumbent="copy", development="failure"),
1099                )
1100        return labels
1101
1102
1103class UnobservableModel(BargainingPowerModel):
1104    """
1105    This model indicates that if the incumbent were not able to observe the entrant at the moment of choosing,
1106    the “kill zone” effect whereby the entrant stays away from the substitute in order to avoid being copied would not take place.
1107    Intuitively, in the game as we studied it so far, the only reason why the entrant is choosing a trajectory leading to another complement
1108    is that it anticipates that if it chose one leading to a substitute, the incumbent would copy, making it an inefficient strategy
1109    for entering the market. However, if the incumbent cannot observe the entrant’s choice of strategy, the entrant could not hope to strategically affect the decision
1110    of the incumbent. This would lead to the entrant having a host of new opportunities when entering the market makes the entrant competing with a large company much more attractive.
1111
1112    Although there may be situations where the entrant could commit to some actions (product design or marketing choices)
1113    which signals that it will not become a rival, and it would have all the incentive to commit to do so,
1114    then the game would be like the sequential moves game analyzed in the basic model.
1115    Otherwise, the entrant will never choose a complement just to avoid copying, and it will enter the “kill zone”.
1116    """
1117
1118    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
1119                 K: float = 0.2, beta: float = 0.5):
1120        """
1121        The parameters do not change compared to Shelegia_Motta_2021.Models.BargainingPowerModel.
1122        """
1123        super(UnobservableModel, self).__init__(u=u, B=B, small_delta=small_delta, delta=delta, K=K, beta=beta)
1124
1125    def plot_incumbent_best_answers(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
1126        return self.plot_equilibrium(axis=axis, **kwargs)
1127
1128    def _create_choice_answer_label(self, entrant: Literal["complement", "substitute", "indifferent"],
1129                                    incumbent: Literal["copy", "refrain"],
1130                                    development: Literal["success", "failure"],
1131                                    kill_zone: bool = False,
1132                                    acquisition: str = "") -> str:
1133        return "{" + self.ENTRANT_CHOICES[entrant] + ", " + self.INCUMBENT_CHOICES[incumbent] + "} $\\rightarrow$ " + \
1134               self.DEVELOPMENT_OUTCOME[development]
1135
1136    def _get_equilibrium_labels(self) -> List[str]:
1137        """
1138        Returns a list containing the labels for the squares in the plot of the equilibrium path.
1139
1140        For the order of the squares refer to the file resources/dev_notes.md.
1141
1142        Returns
1143        -------
1144        List containing the labels for the squares in the plot of the best answers of the equilibrium path.
1145        """
1146        return [
1147            # Area 1
1148            self._create_choice_answer_label(entrant="indifferent", incumbent="copy", development="failure"),
1149            # Area 2
1150            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success"),
1151            # Area 3
1152            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="failure"),
1153            # Area 4
1154            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success")
1155        ]
1156
1157    def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
1158        result: Dict = super().get_optimal_choice(A, F)
1159        # adjust the different choices in area three -> since the kill zone does not exist in this model.
1160        if result["entrant"] == self.ENTRANT_CHOICES["complement"]:
1161            result = {"entrant": self.ENTRANT_CHOICES["substitute"], "incumbent": self.INCUMBENT_CHOICES["copy"],
1162                      "development": self.DEVELOPMENT_OUTCOME["failure"]}
1163        return result
1164
1165
1166class AcquisitionModel(BargainingPowerModel):
1167    """
1168    In order to explore how acquisitions may modify the entrant’s and the incumbent’s strategic choices, we extend the base model
1169    in order to allow an acquisition to take place after the incumbent commits to copying the entrant’s original complementary product
1170    (between t=1 and t=2, see demo.ipynb "Timing of the game"). We assume that the incumbent and the entrant share the gains (if any) attained from the acquisition equally.
1171
1172    The “kill zone” still appears as a possible equilibrium outcome, however for a more reduced region of the parameter space.
1173    The prospect of getting some acquisition gains does tend to increase the profits gained from developing a substitute to the primary product,
1174    and this explains why part of the “kill zone” region where a complement was chosen without the acquisition, the entrant will now choose a substitute instead.
1175    """
1176
1177    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
1178                 K: float = 0.2, beta: float = 0.5) -> None:
1179        """
1180        An additional constraint is added compared to Shelegia_Motta_2021.Models.BaseModel. Namely, $\Delta$ has to be bigger than $u$,
1181        meaning the innovation of the entrant is not too drastic compared with the primary products of the incumbent.
1182
1183        Meanwhile, the parameters do not change compared to Shelegia_Motta_2021.Models.BargainingPowerModel.
1184        """
1185        assert delta < u, "Delta has to be smaller than u, meaning the innovation of the entrant is not too drastic."
1186        super(AcquisitionModel, self).__init__(u=u, B=B, small_delta=small_delta, delta=delta, K=K, beta=beta)
1187        self.ACQUISITION_OUTCOME: Final[Dict[str, str]] = {"merged": "M", "apart": "E"}
1188        """
1189        Contains the options for an acquisition or not.
1190        - merged (M): The incumbent acquired the entrant.
1191        - apart (E): The incumbent did not acquired the entrant.
1192        """
1193
1194    def _calculate_copying_fixed_costs_values(self) -> Dict[str, float]:
1195        copying_fixed_costs_values: Dict[str, float] = super()._calculate_copying_fixed_costs_values()
1196        copying_fixed_costs_values.update(
1197            {'F(ACQ)s': (self._u + self._delta - self._K) / 2 + self._small_delta * (2 - self._beta),
1198             'F(ACQ)c': self._small_delta * (2.5 - 3 * self._beta) - self._K / 2})
1199        assert (abs(copying_fixed_costs_values["F(ACQ)c"] - copying_fixed_costs_values["F(YY)c"]) < self.TOLERANCE or
1200                copying_fixed_costs_values["F(ACQ)c"] < copying_fixed_costs_values["F(YY)c"]), "F(ACQ)c has to be smaller or equal than F(YY)c"
1201        return copying_fixed_costs_values
1202
1203    def get_copying_fixed_costs_values(self) -> Dict[str, float]:
1204        """
1205        Returns the fixed costs for copying thresholds of the incumbent.
1206
1207        Additional thresholds for the fixed cost of copying of the incumbent compared to the Shelegia_Motta_2021.Models.BargainingModel:
1208
1209        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1210        |----------|:-------|:--------|
1211        | $F^{ACQ}_S$ | F(ACQ)s | $\\frac{(u + \Delta - K)}{2} + \delta(2 - \\beta)$ |
1212        | $F^{ACQ}_C$ | F(ACQ)c | $\\frac{K}{2} + \delta(2.5 - 3\\beta)$ |
1213        <br>
1214        As an additional constraint, $F^{ACQ}_C$ has to be smaller or equal than $F^{YY}_C$, since the logic described in the paper may not apply anymore for the other cases.
1215
1216        Returns
1217        -------
1218        Dict[str, float]
1219            Includes the thresholds for the fixed costs for copying of the incumbent.
1220        """
1221        return self._copying_fixed_costs
1222
1223    def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
1224        """
1225        Returns the optimal choice of the entrant and the incumbent based on a pair of assets of the entrant and fixed costs for copying of the incumbent.
1226
1227        The output dictionary will contain the following details:
1228
1229        - "entrant": choice of the entrant (possible choices listed in Shelegia_Motta_2021.IModel.IModel.ENTRANT_CHOICES))
1230        - "incumbent": choice of the incumbent (possible choices listed in Shelegia_Motta_2021.IModel.IModel.INCUMBENT_CHOICES)
1231        - "development": outcome of the development (possible outcomes listed in Shelegia_Motta_2021.IModel.IModel.DEVELOPMENT_OUTCOME)
1232        - "acquisition": outcome of the acquisition (possible outcomes listed in Shelegia_Motta_2021.Models.AcquisitionModel.ACQUISITION_OUTCOME)
1233
1234        To understand the details of the logic implemented, consult the chapter in Shelegia and Motta (2021) corresponding to the model.
1235
1236        Parameters
1237        ----------
1238        A : float
1239            Assets of the entrant.
1240        F : float
1241            Fixed costs for copying of the incumbent.
1242
1243        Returns
1244        -------
1245        Dict[str, str]
1246            Optimal choice of the entrant, the incumbent and the outcome of the development.
1247        """
1248        result: Dict[str, str] = {"entrant": "", "incumbent": "", "development": "", "acquisition": ""}
1249        if self._copying_fixed_costs["F(ACQ)c"] <= F <= self._copying_fixed_costs["F(ACQ)s"] and A < self._assets["A-s"]:
1250            result.update({"entrant": self.ENTRANT_CHOICES["complement"]})
1251            result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
1252            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
1253            result.update({"acquisition": self.ACQUISITION_OUTCOME["apart"]})
1254        elif F < self._copying_fixed_costs["F(ACQ)c"] and A < self._assets["A-s"]:
1255            # to develop a substitute is the weakly dominant strategy of the entrant
1256            entrant_choice_area_1: Literal["substitute", "complement"] = "substitute"
1257            # if the payoff for a complement is higher than for a substitute, the entrant will choose the complement.
1258            if self._delta < self._small_delta:
1259                entrant_choice_area_1 = "complement"
1260            result.update({"entrant": self.ENTRANT_CHOICES[entrant_choice_area_1]})
1261            result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
1262            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
1263            result.update({"acquisition": self.ACQUISITION_OUTCOME["merged"]})
1264        else:
1265            result.update({"entrant": self.ENTRANT_CHOICES["substitute"]})
1266            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
1267            result.update({"acquisition": self.ACQUISITION_OUTCOME["merged"]})
1268            if F <= self._copying_fixed_costs["F(YY)c"]:
1269                result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
1270            else:
1271                result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
1272        return result
1273
1274    def _get_incumbent_best_answer_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
1275        y_max: float = self._get_y_max(y_max)
1276        x_max: float = self._get_x_max(x_max)
1277        return [
1278            # Area 1
1279            [(0, 0), (self._assets['A-c'], 0), (self._assets['A-c'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1280             (0, max(self._copying_fixed_costs['F(ACQ)c'], 0))],
1281            # Area 2
1282            [(self._assets['A-c'], 0), (x_max, 0), (x_max, self._copying_fixed_costs['F(YY)c']),
1283             (self._assets['A-c'], self._copying_fixed_costs['F(YY)c'])],
1284            # Area 3
1285            [(0, max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1286             (self._assets['A-s'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1287             (self._assets['A-s'], self._copying_fixed_costs['F(ACQ)s']), (0, self._copying_fixed_costs['F(ACQ)s'])],
1288            # Area 4
1289            [(self._assets['A-s'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1290             (self._assets['A-c'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1291             (self._assets['A-c'], self._copying_fixed_costs['F(YY)c']),
1292             (self._assets['A-s'], self._copying_fixed_costs['F(YY)c'])],
1293            # Area 5
1294            [(self._assets['A-s'], self._copying_fixed_costs['F(YY)c']), (x_max, self._copying_fixed_costs['F(YY)c']),
1295             (x_max, y_max),
1296             (0, y_max), (0, self._copying_fixed_costs['F(ACQ)s']),
1297             (self._assets['A-s'], self._copying_fixed_costs['F(ACQ)s'])]]
1298
1299    def _get_incumbent_best_answer_labels(self) -> List[str]:
1300        return [
1301            # Area 1
1302            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1303                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1304            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="success",
1305                                             acquisition=self.ACQUISITION_OUTCOME["merged"]),
1306            # Area 2
1307            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1308                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1309            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="success",
1310                                             acquisition=self.ACQUISITION_OUTCOME["apart"]),
1311            # Area 3
1312            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1313                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1314            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
1315                                             acquisition=self.ACQUISITION_OUTCOME["apart"]),
1316            # Area 4
1317            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1318                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1319            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
1320                                             acquisition=self.ACQUISITION_OUTCOME["apart"]),
1321            # Area 5
1322            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success",
1323                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1324            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
1325                                             acquisition=self.ACQUISITION_OUTCOME["apart"]),
1326        ]
1327
1328    def _get_equilibrium_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
1329        y_max: float = self._get_y_max(y_max)
1330        x_max: float = self._get_x_max(x_max)
1331        return [
1332            # Area 1
1333            [(0, 0), (self._assets['A-s'], 0), (self._assets['A-s'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1334             (0, max(self._copying_fixed_costs['F(ACQ)c'], 0))],
1335            # Area 2
1336            [(self._assets['A-s'], 0), (x_max, 0), (x_max, self._copying_fixed_costs['F(YY)c']),
1337             (self._assets['A-s'], self._copying_fixed_costs['F(YY)c'])],
1338            # Area 3
1339            [(0, max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1340             (self._assets['A-s'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1341             (self._assets['A-s'], self._copying_fixed_costs['F(ACQ)s']), (0, self._copying_fixed_costs['F(ACQ)s'])],
1342            # Area 4
1343            [(self._assets['A-s'], self._copying_fixed_costs['F(YY)c']), (x_max, self._copying_fixed_costs['F(YY)c']),
1344             (x_max, y_max), (0, y_max), (0, self._copying_fixed_costs['F(ACQ)s']),
1345             (self._assets['A-s'], self._copying_fixed_costs['F(ACQ)s'])]]
1346
1347    def _get_equilibrium_labels(self) -> List[str]:
1348        # to develop a substitute is the weakly dominant strategy of the entrant
1349        entrant_choice_area_1: Literal["substitute", "complement"] = "substitute"
1350        # if the payoff for a complement is higher than for a substitute, the entrant will choose the complement.
1351        if self._delta < self._small_delta:
1352            entrant_choice_area_1 = "complement"
1353        return [
1354            # Area 1
1355            self._create_choice_answer_label(entrant=entrant_choice_area_1, incumbent="copy", development="success",
1356                                             acquisition=self.ACQUISITION_OUTCOME["merged"]),
1357            # Area 2
1358            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1359                                             acquisition=self.ACQUISITION_OUTCOME["merged"]),
1360            # Area 3
1361            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
1362                                             kill_zone=True, acquisition=self.ACQUISITION_OUTCOME["apart"]),
1363            # Area 4
1364            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success",
1365                                             acquisition=self.ACQUISITION_OUTCOME["merged"])
1366        ]
1367
1368    def _draw_thresholds(self, axis: matplotlib.axes.Axes, x_horizontal: float = 0, y_vertical: float = 0) -> None:
1369        self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(ACQ)s'], label="$F^{ACQ}_S$",
1370                                              x=x_horizontal)
1371        self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YN)s'], label="$F^{YN}_S$",
1372                                              x=x_horizontal)
1373
1374        if abs(self._copying_fixed_costs['F(YY)c'] - self._copying_fixed_costs['F(ACQ)c']) < self.TOLERANCE:
1375            self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(ACQ)c'], x=x_horizontal,
1376                                                  label="$F^{ACQ}_C=F^{YY}_C$")
1377        else:
1378            if self._copying_fixed_costs['F(ACQ)c'] >= 0:
1379                self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(ACQ)c'], x=x_horizontal,
1380                                                      label="$F^{ACQ}_C$")
1381            self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YY)c'], label="$F^{YY}_C$",
1382                                                      x=x_horizontal)
1383        # vertical lines (asset thresholds)
1384        self._draw_vertical_line_with_label(axis, x=self._assets['A-s'], label=r'$\bar{A}_S$', y=y_vertical)
1385        self._draw_vertical_line_with_label(axis, x=self._assets['A-c'], label=r'$\bar{A}_C$', y=y_vertical)
1386
1387    @staticmethod
1388    def _create_cost_thresholds_legend(width: int) -> str:
1389        legend: str = super(AcquisitionModel, AcquisitionModel)._create_cost_thresholds_legend(width=width)
1390        return legend + "\n" + \
1391               AcquisitionModel._format_legend_line(r'$F^{ACQ}_C$' + ": Maximum level of fixed costs that ensure that the incumbent acquires the entrant if the entrant develops a second complement.", width=width) + "\n" + \
1392               AcquisitionModel._format_legend_line(r'$F^{ACQ}_S$' + ": Maximum level of fixed costs that ensure that the incumbent acquires the entrant if the entrant develops a perfect substitute.", width=width)
1393
1394    def _create_options_legend(self, width: int) -> str:
1395        legend: str = super(AcquisitionModel, self)._create_options_legend(width=width)
1396        # modify outcomes without acquisition
1397        legend = legend.replace(self.DEVELOPMENT_OUTCOME['success'], "$" + self.DEVELOPMENT_OUTCOME['success'] + "_" + self.ACQUISITION_OUTCOME["apart"] + "$")
1398        legend = legend.replace(self.DEVELOPMENT_OUTCOME['failure'], "$" + self.DEVELOPMENT_OUTCOME['failure'] + "_" + self.ACQUISITION_OUTCOME["apart"] + "$")
1399
1400        # add additional outcomes with acquisition
1401        return legend + "\n" + \
1402               self._format_legend_line("$" + self.DEVELOPMENT_OUTCOME['success'] + "_" + self.ACQUISITION_OUTCOME["merged"] + "$ : The merged entity has sufficient assets to develop the product.", width=width) + "\n" + \
1403               self._format_legend_line("$" + self.DEVELOPMENT_OUTCOME['failure'] + "_" + self.ACQUISITION_OUTCOME["merged"] + "$ : The merged entity has not sufficient assets to develop the product.", width=width)
1404
1405
1406if __name__ == '__main__':
1407    model: Shelegia_Motta_2021.IModel = Shelegia_Motta_2021.AcquisitionModel()
1408    print(model)
class BaseModel(Shelegia_Motta_2021.IModel.IModel):
 22class BaseModel(Shelegia_Motta_2021.IModel):
 23    """
 24    The base model of the project consists of two players: The incumbent, which sells the primary product,
 25    and a start-up otherwise known as the entrant which sells a complementary product to the incumbent.
 26    One way to visualize a real-world application of this model would be to think of the entrant as a product or service
 27    that can be accessed through the platform of the incumbent, like a plug in that can be accessed through Google or a game on Facebook.
 28    The aim of this model is to monitor the choice that the entrant has between developing a substitute to or
 29    another compliment to the incumbent. The second aim is to observe the choice of the incumbent of whether
 30    to copy the original complementary product of the entrant by creating a perfect substitute or not.
 31    Seeing as the entrant may not have enough assets to fund a second product, the incumbent copying its first product
 32    would inhibit the entrant’s ability to fund its projects. This report will illustrate how the incumbent has a strategic incentive to copy
 33    the entrant if it is planning to compete and that it would refrain from copying if the entrant plans to develop a compliment.
 34    The subsequent models included in this report will introduce additional factors but will all be based on the basic model.
 35
 36    The equilibrium path arguably supports the “kill zone” argument: due to the risk of an exclusionary strategy by the incumbent,
 37    a potential entrant may prefer to avoid a market trajectory which would lead it to compete with the core product of a dominant incumbent
 38    and would choose to develop another complementary product instead.
 39    """
 40
 41    TOLERANCE: Final[float] = 10 ** (-10)
 42    """Tolerance for the comparison of two floating numbers."""
 43
 44    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
 45                 K: float = 0.2) -> None:
 46        """
 47        Initializes a valid BaseModel object.
 48
 49        The following preconditions have to be satisfied:
 50        - (A1b) $\delta$ / 2 < $\Delta$ < 3 * $\delta$ / 2
 51        - (A2) K < $\delta$ / 2
 52
 53        Parameters
 54        ----------
 55        u : float
 56            Utility gained from consuming the primary product.
 57        B : float
 58            Minimal difference between the return in case of a success and the return in case of failure of E. B is called the private benefit of the entrant in case of failure.
 59        small_delta : float
 60            ($\delta$) Additional utility gained from a complement combined with a primary product.
 61        delta : float
 62            ($\Delta$) Additional utility gained from the substitute of the entrant compared to the primary product of the incumbent.
 63        K : float
 64            Investment costs for the entrant to develop a second product.
 65        """
 66        super(BaseModel, self).__init__()
 67        assert small_delta / 2 < delta < 3 * small_delta / 2, "(A1b) not satisfied."
 68        assert K < small_delta / 2, "(A2) not satisfied."
 69        self._u: float = u
 70        self._B: float = B
 71        self._small_delta: float = small_delta
 72        self._delta: float = delta
 73        self._K: float = K
 74        self._copying_fixed_costs: Dict[str, float] = self._calculate_copying_fixed_costs_values()
 75        self._assets: Dict[str, float] = self._calculate_asset_values()
 76        self._payoffs: Dict[str, Dict[str, float]] = self._calculate_payoffs()
 77
 78    def _calculate_payoffs(self) -> Dict[str, Dict[str, float]]:
 79        """
 80        Calculates the payoffs for different market configurations with the formulas given in the paper.
 81
 82        The formulas are tabulated in BaseModel.get_payoffs.
 83
 84        Returns
 85        -------
 86        Dict[str, Dict[str, float]]
 87            Contains the mentioned payoffs for different market configurations.
 88        """
 89        return {'basic': {'pi(I)': self._u + self._small_delta / 2,
 90                          'pi(E)': self._small_delta / 2,
 91                          'CS': 0,
 92                          'W': self._u + self._small_delta
 93                          },
 94                'I(C)': {'pi(I)': self._u + self._small_delta,
 95                         'pi(E)': 0,
 96                         'CS': 0,
 97                         'W': self._u + self._small_delta
 98                         },
 99                'E(P)': {'pi(I)': 0,
100                         'pi(E)': self._delta + self._small_delta,
101                         'CS': self._u,
102                         'W': self._u + self._delta + self._small_delta
103                         },
104                'I(C)E(P)': {'pi(I)': 0,
105                             'pi(E)': self._delta,
106                             'CS': self._u + self._small_delta,
107                             'W': self._u + self._delta + self._small_delta
108                             },
109                'E(C)': {'pi(I)': self._u + self._small_delta,
110                         'pi(E)': self._small_delta,
111                         'CS': 0,
112                         'W': self._u + 2 * self._small_delta
113                         },
114                'I(C)E(C)': {'pi(I)': self._u + 3 / 2 * self._small_delta,
115                             'pi(E)': self._small_delta / 2,
116                             'CS': 0,
117                             'W': self._u + 2 * self._small_delta
118                             }
119                }
120
121    def _calculate_copying_fixed_costs_values(self) -> Dict[str, float]:
122        """
123        Calculates the thresholds for the fixed costs of copying for the incumbent.
124
125        The formulas are tabulated in BaseModel.get_copying_fixed_costs_values.
126
127        Returns
128        -------
129        Dict[str, float]
130            Includes the thresholds for the fixed costs for copying of the incumbent.
131        """
132        return {'F(YY)s': self._small_delta / 2,
133                'F(YN)s': self._u + self._small_delta * 3 / 2,
134                'F(YY)c': self._small_delta,
135                'F(YN)c': self._small_delta / 2}
136
137    def _calculate_asset_values(self) -> Dict[str, float]:
138        """
139        Calculates the thresholds for the assets of the entrant.
140
141        The formulas are tabulated in BaseModel.get_asset_values.
142
143        Returns
144        -------
145        Dict[str, float]
146            Includes the thresholds for the assets of the entrant.
147        """
148        return {'A_s': self._B - (self._delta + 3 / 2 * self._small_delta - self._K),
149                'A_c': self._B - (3 / 2 * self._small_delta - self._K),
150                'A-s': self._B - (self._delta - self._K),
151                'A-c': self._B - (1 / 2 * self._small_delta - self._K)}
152
153    def get_asset_values(self) -> Dict[str, float]:
154        """
155        Returns the asset thresholds of the entrant.
156
157        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
158        |----------------|:----------|:-----------|
159        | $\\underline{A}_S$ | A_s | $(2)\: B + K - \Delta - 3\delta/2$ |
160        | $\\underline{A}_C$ | A_c | $(3)\: B + K - 3\delta/2$ |
161        | $\overline{A}_S$ | A-s | $(4)\: B + K - \Delta$ |
162        | $\overline{A}_C$ | A-c | $(5)\: B + K - \delta/2$ |
163        <br>
164        Returns
165        -------
166        Dict[str, float]
167            Includes the thresholds for the assets of the entrant.
168        """
169        return self._assets
170
171    def get_copying_fixed_costs_values(self) -> Dict[str, float]:
172        """
173        Returns the fixed costs for copying thresholds of the incumbent.
174
175        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
176        |----------|:-------|:--------|
177        | $F^{YY}_S$ | F(YY)s | $(6)\: \delta/2$ |
178        | $F^{YN}_S$ | F(YN)s | $(6)\: u + 3\delta/2$ |
179        | $F^{YY}_C$ | F(YY)c | $(6)\: \delta$ |
180        | $F^{YN}_C$ | F(YN)c | $(6)\: \delta/2$ |
181        <br>
182        Returns
183        -------
184        Dict[str, float]
185            Includes the thresholds for the fixed costs for copying of the incumbent.
186        """
187        return self._copying_fixed_costs
188
189    def get_payoffs(self) -> Dict[str, Dict[str, float]]:
190        """
191        Returns the payoffs for different market configurations.
192
193        A market configuration can include:
194        - $I_P$ : Primary product sold by the incumbent.
195        - $I_C$ : Complementary product to $I_P$ potentially sold by the incumbent, which is copied from $E_C$.
196        - $E_P$ : Perfect substitute to $I_P$ potentially sold by the entrant.
197        - $E_C$ : Complementary product to $I_P$ currently sold by the entrant
198        - $\\tilde{E}_C$ : Complementary product to $I_P$ potentially sold by the entrant.
199        <br>
200
201        | Market Config. $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(I) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(E) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | CS $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | W $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
202        |-----------------------|:--------|:--------|:--|:-|
203        | $I_P$ ; $E_C$         | $u + \delta/2$ | $\delta/2$ | 0 | $u + \delta$ |
204        | $I_P + I_C$ ; $E_C$   | $u + \delta$ | 0 | 0 | $u + \delta$ |
205        | $I_P$ ; $E_P + E_C$   | 0 | $\Delta + \delta$ | $u$ | $u + \Delta + \delta$ |
206        | $I_P + I_C$ ; $E_P + E_C$ | 0 | $\Delta$ | $u + \delta$ | $u + \Delta + \delta$ |
207        | $I_P$ ; $E_C + \\tilde{E}_C$ | $u + \delta$ | $\delta$ | 0 | $u + 2\delta$ |
208        | $I_P + I_C$ ; $E_C + \\tilde{E}_C$ | $u + 3\delta/2$ | $\delta/2$ | 0 | $u + 2\delta$ |
209        <br>
210
211        Returns
212        -------
213        Dict[str, Dict[str, float]]
214            Contains the mentioned payoffs for different market configurations.
215        """
216        return self._payoffs
217
218    def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
219        result: Dict[str, str] = {"entrant": "", "incumbent": "", "development": ""}
220        if self._copying_fixed_costs["F(YN)c"] <= F <= self._copying_fixed_costs["F(YN)s"] and A < self._assets["A-s"]:
221            result.update({"entrant": self.ENTRANT_CHOICES["complement"]})
222            result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
223            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
224        elif F <= self._copying_fixed_costs["F(YN)c"] and A < self._assets["A-s"]:
225            result.update({"entrant": self.ENTRANT_CHOICES["indifferent"]})
226            result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
227            result.update({"development": self.DEVELOPMENT_OUTCOME["failure"]})
228        else:
229            result.update({"entrant": self.ENTRANT_CHOICES["substitute"]})
230            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
231            if F <= self._copying_fixed_costs["F(YY)s"]:
232                result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
233            else:
234                result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
235        return result
236
237    def _plot(self, coordinates: List[List[Tuple[float, float]]], labels: List[str],
238              axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
239        """
240        Plots the areas containing the optimal choices and answers into a coordinate system.
241
242        Parameters
243        ----------
244        coordinates : List[List[Tuple[float, float]]]
245            List of all polygons (list of coordinates) to plot.
246        labels: List[str]
247            List containing all the labels for the areas.
248        axis : matplotlib.axes.Axes
249            Axis to draw the plot on. (optional)
250        **kwargs
251            Optional key word arguments for the plots.<br>
252            - title: title of the plot.<br>
253            - xlabel: label for the x - axis.<br>
254            - ylabel: label for the y - axis.<br>
255            - options_legend: If true, an additional legend, explaining the options of the entrant and the incumbent, will be added to the plot.<br>
256            - asset_legend: If true, an additional legend explaining the thresholds of the assets of the entrant will be added to the plot.<br>
257            - costs_legend: If true, an additional legend explaining the thresholds of the fixed costs of copying for the incumbent will be added to the plot.<br>
258            - legend_width : Maximum number of characters in one line in the legend (for adjustments to figure width).<br>
259            - x_max : Maximum number plotted on the x - axis.<br>
260            - y_max : Maximum number plotted on the y - axis.<br>
261
262        Returns
263        -------
264        Axis containing the plot.
265        """
266        if axis is None:
267            plot_fig, axis = plt.subplots()
268        self._draw_thresholds(axis, x_horizontal=kwargs.get("x_max", 0), y_vertical=kwargs.get("y_max", 0))
269
270        for i, coordinates in enumerate(coordinates):
271            poly = plt.Polygon(coordinates, linewidth=0, color=self._get_color(i), label=labels[i])
272            axis.add_patch(poly)
273
274        if kwargs.get("legend", True):
275            axis.legend(bbox_to_anchor=(1.3, 1), loc="upper left")
276            additional_legend: str = self._create_additional_legend(options_legend=kwargs.get('options_legend', False),
277                                                                    assets_thresholds_legend=kwargs.get('asset_legend', False),
278                                                                    costs_thresholds_legend=kwargs.get('costs_legend', False),
279                                                                    width=kwargs.get('legend_width', 60))
280            if additional_legend != "":
281                axis.text(-0.1, -0.6, additional_legend, verticalalignment='top', linespacing=1, wrap=True)
282
283        BaseModel._set_axis_labels(axis, title=kwargs.get('title', ''),
284                                   x_label=kwargs.get('xlabel', 'Assets of the entrant'),
285                                   y_label=kwargs.get('ylabel', 'Fixed costs of copying for the incumbent'))
286        BaseModel._set_axis(axis)
287        return axis
288
289    def plot_incumbent_best_answers(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
290        poly_coordinates: List[List[Tuple[float, float]]] = self._get_incumbent_best_answer_coordinates(
291            kwargs.get("x_max", 0),
292            kwargs.get("y_max", 0))
293        poly_labels: List[str] = self._get_incumbent_best_answer_labels()
294        kwargs.update({'title': kwargs.get('title', "Best Answers of the incumbent to the choices of the entrant")})
295        return self._plot(coordinates=poly_coordinates, labels=poly_labels, axis=axis, **kwargs)
296
297    def _create_choice_answer_label(self, entrant: Literal["complement", "substitute", "indifferent"],
298                                    incumbent: Literal["copy", "refrain"],
299                                    development: Literal["success", "failure"],
300                                    kill_zone: bool = False, acquisition: str = "") -> str:
301        """
302        Creates a label for the legend based on the choice of the entrant, the incumbent, the development outcome and additionally on possible acquisition.
303
304        Parameters
305        ----------
306        entrant: Literal["complement", "substitute", "indifferent"]
307            choice of the entrant.
308        incumbent: Literal["copy", "refrain"]
309            choice of the incumbent.
310        development: Literal["success", "failure"]
311            outcome of the development.
312        kill_zone: bool
313            If true, the label adds a "(Kill Zone)" tag.
314        acquisition: str
315            The entity, which develops the additional product chosen by the entrant.
316
317        Returns
318        -------
319        str
320            label based on the parameters mentioned above.
321        """
322        if acquisition != "":
323            acquisition = "_" + acquisition
324        return self.ENTRANT_CHOICES[entrant] + " $\\rightarrow$ " + self.INCUMBENT_CHOICES[
325            incumbent] + " $\\rightarrow " + self.DEVELOPMENT_OUTCOME[development] + acquisition + "$" + (
326                   "\n(Kill Zone)" if kill_zone else "")
327
328    def _get_incumbent_best_answer_labels(self) -> List[str]:
329        """
330        Returns a list containing the labels for the squares in the plot of the best answers of the incumbent to the choice of the entrant.
331
332        For the order of the labels refer to the file resources/dev_notes.md.
333
334        Returns
335        -------
336        List containing the labels for the squares in the plot of the best answers of the incumbent to the choice of the entrant.
337        """
338        return [
339            # Area 1
340            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="failure") + " \n" +
341            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="failure"),
342            # Area 2
343            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success") + " \n" +
344            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="failure"),
345            # Area 3
346            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success") + " \n" +
347            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="success"),
348            # Area 4
349            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="failure") + " \n" +
350            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success"),
351            # Area 5
352            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success") + " \n" +
353            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="success"),
354            # Area 6
355            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success") + " \n" +
356            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success"),
357        ]
358
359    def _get_incumbent_best_answer_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
360        """
361        Returns a list containing the coordinates for the areas in the plot of the best answers of the incumbent to the choice of the entrant.
362
363        For the order of the areas refer to the file resources/dev_notes.md.
364
365        Returns
366        -------
367        List[List[Tuple[float, float]]]
368            List containing the coordinates for the areas in the plot of the best answers of the incumbent to the choice of the entrant.
369        """
370        y_max = self._get_y_max(y_max)
371        x_max = self._get_x_max(x_max)
372        return [
373            # Area 1
374            [(0, 0), (self._assets['A-s'], 0), (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0)),
375             (0, max(self._copying_fixed_costs['F(YN)c'], 0))],
376            # Area 2
377            [(self._assets['A-s'], 0), (self._assets['A-c'], 0),
378             (self._assets['A-c'], self._copying_fixed_costs['F(YY)s']),
379             (self._assets['A-s'], self._copying_fixed_costs['F(YY)s'])],
380            # Area 3
381            [(self._assets['A-c'], 0), (x_max, 0), (x_max, self._copying_fixed_costs['F(YY)s']),
382             (self._assets['A-c'], self._copying_fixed_costs['F(YY)s'])],
383            # Area 4
384            [(0, max(self._copying_fixed_costs['F(YN)c'], 0)),
385             (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0)),
386             (self._assets['A-s'], self._copying_fixed_costs['F(YN)s']), (0, self._copying_fixed_costs['F(YN)s'])],
387            # Area 5
388            [(self._assets['A-c'], self._copying_fixed_costs['F(YY)s']), (x_max, self._copying_fixed_costs['F(YY)s']),
389             (x_max, self._copying_fixed_costs['F(YY)c']), (self._assets['A-c'], self._copying_fixed_costs['F(YY)c'])],
390            # Area 6
391            [(self._assets['A-s'], self._copying_fixed_costs['F(YY)s']),
392             (self._assets['A-c'], self._copying_fixed_costs['F(YY)s']),
393             (self._assets['A-c'], self._copying_fixed_costs['F(YY)c']), (x_max, self._copying_fixed_costs['F(YY)c']),
394             (x_max, y_max), (0, y_max),
395             (0, self._copying_fixed_costs['F(YN)s']), (self._assets['A-s'], self._copying_fixed_costs['F(YN)s'])]]
396
397    def plot_equilibrium(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
398        poly_coordinates: List[List[Tuple[float, float]]] = self._get_equilibrium_coordinates(kwargs.get("x_max", 0),
399                                                                                              kwargs.get("y_max", 0))
400        poly_labels: List[str] = self._get_equilibrium_labels()
401        kwargs.update({'title': kwargs.get('title', 'Equilibrium Path')})
402        return self._plot(coordinates=poly_coordinates, labels=poly_labels, axis=axis, **kwargs)
403
404    def _get_equilibrium_labels(self) -> List[str]:
405        """
406        Returns a list containing the labels for the squares in the plot of the equilibrium path.
407
408        For the order of the squares refer to the file resources/dev_notes.md.
409
410        Returns
411        -------
412        List[str]
413            List containing the labels for the squares in the plot of the best answers of the equilibrium path.
414        """
415        return [
416            # Area 1
417            self._create_choice_answer_label(entrant="indifferent", incumbent="copy", development="failure"),
418            # Area 2
419            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success"),
420            # Area 3
421            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
422                                             kill_zone=True),
423            # Area 4
424            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success")
425        ]
426
427    def _get_equilibrium_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
428        """
429        Returns a list containing the coordinates for the areas in the plot of the equilibrium path.
430
431        For the order of the areas refer to the file resources/dev_notes.md.
432
433        Returns
434        -------
435        List[List[Tuple[float, float]]]
436            List containing the coordinates for the areas in the plot of the best answers of the equilibrium path.
437        """
438        y_max = self._get_y_max(y_max)
439        x_max = self._get_x_max(x_max)
440        return [
441            # Area 1
442            [(0, 0), (self._assets['A-s'], 0), (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0)),
443             (0, max(self._copying_fixed_costs['F(YN)c'], 0))],
444            # Area 2
445            [(self._assets['A-s'], 0), (x_max, 0), (x_max, self._copying_fixed_costs['F(YY)s']),
446             (self._assets['A-s'], self._copying_fixed_costs['F(YY)s'])],
447            # Area 3
448            [(0, max(self._copying_fixed_costs['F(YN)c'], 0)),
449             (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0)),
450             (self._assets['A-s'], self._copying_fixed_costs['F(YN)s']), (0, self._copying_fixed_costs['F(YN)s'])],
451            # Area 4
452            [(self._assets['A-s'], self._copying_fixed_costs['F(YY)s']), (x_max, self._copying_fixed_costs['F(YY)s']),
453             (x_max, y_max), (0, y_max), (0, self._copying_fixed_costs['F(YN)s']),
454             (self._assets['A-s'], self._copying_fixed_costs['F(YN)s'])]]
455
456    def plot_payoffs(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
457        if axis is None:
458            plot_fig, axis = plt.subplots()
459        index = arange(0, len(self._payoffs) * 2, 2)
460        bar_width = 0.35
461        spacing = 0.05
462
463        self._plot_payoffs_bars(axis, bar_width, index, spacing, **kwargs)
464
465        axis.set_xlabel('Market Configurations')
466        axis.set_title('Payoffs for different Market Configurations')
467        self._set_payoffs_ticks(axis, bar_width, index, spacing)
468        if kwargs.get("legend", True):
469            self._set_payoff_legend(axis, kwargs.get("products_legend", False))
470        self._set_payoffs_figure(axis)
471        return axis
472
473    def _plot_payoffs_bars(self, axis: matplotlib.axes.Axes, bar_width: float, index: array, spacing: float,
474                           **kwargs) -> None:
475        """
476        Plots the bars representing the payoffs for different market configurations of different stakeholders on the specified axis.
477
478        Parameters
479        ----------
480        axis matplotlib.axes.Axes
481            To plot the bars on.
482        bar_width: float
483            Width of a bar in the plot.
484        index: np.array
485            Index of the different market configurations in the plot.
486        spacing: float
487            Spacing between the bars on the plot.
488        **kwargs
489            Optional key word arguments for the payoff plot.<br>
490            - opacity : Opacity of the not optimal payoffs.<br>
491        """
492        for counter, utility_type in enumerate(self._payoffs[list(self._payoffs.keys())[0]].keys()):
493            utility_values: List[float] = []
494            for market_configuration in self._payoffs:
495                utility_values.append(self._payoffs[market_configuration][utility_type])
496
497            bars = axis.bar(index + counter * (bar_width + spacing), utility_values, bar_width,
498                            alpha=kwargs.get("opacity", 0.2),
499                            color=self._get_color(counter),
500                            edgecolor=None,
501                            label=self._convert_payoffs_label(utility_type))
502            max_indices: List[int] = list(
503                filter(lambda x: utility_values[x] == max(utility_values), range(len(utility_values))))
504            for max_index in max_indices:
505                bars[max_index].set_alpha(1)
506
507    def _set_payoff_legend(self, axis: matplotlib.axes.Axes, products_legend: bool = False) -> None:
508        """
509        Creates the legend and an additional legend for the products of the entrant and the incumbent,
510
511        Parameters
512        ----------
513        axis: matplotlib.axes.Axes
514            To set the legends for.
515        products_legend: bool
516            If true, an additional legend, containing all possible products of the entrant and the incumbent, will be created.
517        """
518        axis.legend(bbox_to_anchor=(1.02, 1), loc='upper left', ncol=1)
519        if products_legend:
520            axis.text(-0.7, -0.8, self._get_market_configuration_annotations(), verticalalignment="top")
521
522    def _set_payoffs_ticks(self, axis: matplotlib.axes.Axes, bar_width: float, index: array, spacing: float) -> None:
523        """
524        Sets the x - and y - ticks for the plot of the payoffs for different market configurations.
525
526        Parameters
527        ----------
528        axis matplotlib.axes.Axes
529            To adjust the ticks on.
530        bar_width: float
531            Width of a bar in the plot.
532        index: np.array
533            Index of the different market configurations in the plot.
534        spacing: float
535            Spacing between the bars on the plot.
536        """
537        axis.set(yticklabels=[])
538        axis.tick_params(left=False)
539        axis.set_xticks(index + 1.5 * (bar_width + spacing))
540        axis.set_xticklabels(tuple([self._convert_market_configuration_label(i) for i in self._payoffs.keys()]))
541
542    @staticmethod
543    def _set_payoffs_figure(axis: matplotlib.axes.Axes) -> None:
544        """
545        Adjust the matplotlib figure to plot the payoffs for different market configurations.
546
547        Parameters
548        ----------
549        axis: matplotlib.axes.Axes
550            To adjust for the payoff plot.
551        """
552        axis.figure.set_size_inches(10, 5)
553        axis.figure.tight_layout()
554
555    @staticmethod
556    def _get_market_configuration_annotations() -> str:
557        """
558        Returns a string containing all product options for the entrant and the incumbent.
559
560        Returns
561        -------
562        str
563            Contains all product options for the entrant and the incumbent.
564        """
565        return "$I_P$: Primary product sold by the incumbent\n" \
566               "$I_C$: Copied complementary product to $I_P$ potentially sold by the incumbent\n" \
567               "$E_P$: Perfect substitute to $I_P$ potentially sold by the entrant\n" \
568               "$E_C$: Complementary product to $I_P$ currently sold by the entrant\n" \
569               "$\\tilde{E}_C$: Complementary product to $I_P$ potentially sold by the entrant\n" \
570               "\nThe bars representing the maximum payoff for a stakeholder are fully filled."
571
572    @staticmethod
573    def _convert_payoffs_label(raw_label: str) -> str:
574        """
575        Converts keys of the payoffs dict to latex labels.
576
577        Parameters
578        ----------
579        raw_label: str
580            As given as key in the payoffs dict.
581
582        Returns
583        -------
584        str
585            Latex compatible pretty label.
586        """
587        label: str = raw_label.replace("pi", "$\pi$")
588        label = label.replace("CS", "Consumer Surplus")
589        label = label.replace("W", "Welfare")
590        return label
591
592    @staticmethod
593    def _convert_market_configuration_label(raw_label: str) -> str:
594        """
595        Returns the latex string for a specific market configuration.
596
597        Parameters
598        ----------
599        raw_label
600            Of the market configuration as given as key in the payoffs dict.
601
602        Returns
603        -------
604        str
605            Corresponding latex label for the market configuration as given as key in the payoffs dict.
606        """
607        labels: Dict[str] = {"basic": "$I_P;E_C$",
608                             "I(C)": "$I_P+I_C;E_C$",
609                             "E(P)": "$I_P;E_C+E_P$",
610                             "I(C)E(P)": "$I_P+I_C;E_C+E_P$",
611                             "E(C)": "$I_P;E_C+\\tilde{E}_C$",
612                             "I(C)E(C)": "$I_P+I_C;E_C+\\tilde{E}_C$"}
613        return labels.get(raw_label, 'No valid market configuration')
614
615    def _get_x_max(self, x_max: float = 0) -> float:
616        """
617        Returns the maximum value to plot on the x - axis.
618
619        Parameters
620        ----------
621        x_max: float
622            Preferred value for the maximum value on the x - axis.
623
624        Returns
625        -------
626        float
627            Maximum value (which is feasible) to plot on the x - axis.
628        """
629        auto_x_max: float = round(self._assets['A-c'] * 1.3, 1)
630        return x_max if x_max > self._assets['A-c'] else auto_x_max
631
632    def _get_y_max(self, y_max: float = 0) -> float:
633        """
634        Returns the maximum value to plot on the y - axis.
635
636        Parameters
637        ----------
638        y_max: float
639            Preferred value for the maximum value on the y - axis.
640
641        Returns
642        -------
643        float
644            Maximum value (which is feasible) to plot on the y - axis.
645        """
646        auto_y_max: float = round(self._copying_fixed_costs['F(YN)s'] * 1.3, 1)
647        return y_max if y_max > self._copying_fixed_costs['F(YN)s'] else auto_y_max
648
649    def _draw_thresholds(self, axis: matplotlib.axes.Axes, x_horizontal: float = 0, y_vertical: float = 0) -> None:
650        """
651        Draws the thresholds and the corresponding labels on a given axis.
652
653        Parameters
654        ----------
655        axis: matplotlib.axes.Axes
656            Axis to draw the thresholds on.
657        x_horizontal : float
658            X - coordinate for horizontal thresholds labels (fixed costs of copying).
659        y_vertical : float
660            Y - coordinate for vertical thresholds labels (assets of the entrant).
661        """
662        # horizontal lines (fixed cost of copying thresholds)
663        self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YN)s'], label="$F^{YN}_S$",
664                                              x=x_horizontal)
665        self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YY)c'], label="$F^{YY}_C$",
666                                              x=x_horizontal)
667        if abs(self._copying_fixed_costs['F(YY)s'] - self._copying_fixed_costs['F(YN)c']) < BaseModel.TOLERANCE:
668            self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YY)s'],
669                                                  label="$F^{YY}_S=F^{YN}_C$", x=x_horizontal)
670        else:
671            self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YY)s'], label="$F^{YY}_S$",
672                                                  x=x_horizontal)
673            if self._copying_fixed_costs['F(YN)c'] >= 0:
674                self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YN)c'], label="$F^{YN}_C$",
675                                                      x=x_horizontal)
676        # vertical lines (asset thresholds)
677        self._draw_vertical_line_with_label(axis, x=self._assets['A-s'], label=r'$\bar{A}_S$', y=y_vertical)
678        self._draw_vertical_line_with_label(axis, x=self._assets['A-c'], label=r'$\bar{A}_C$', y=y_vertical)
679
680    def _draw_horizontal_line_with_label(self, axis: matplotlib.axes.Axes, y: float, **kwargs) -> None:
681        """
682        Draws a horizontal line at a given y - coordinate and writes the corresponding label at the edge.
683
684        Parameters
685        ----------
686        axis
687            To draw the horizontal line and label on.
688        y
689            Coordinate of the of the line on the y - axis.
690        **kwargs
691            Optional key word arguments for the equilibrium plot.<br>
692            - label: Label for the horizontal line written at the edge.<br>
693            - x: X - coordinate for horizontal thresholds labels (fixed costs of copying).<br>
694        """
695        label_x: float = self._get_x_max(kwargs.get("x", 0)) + 0.05
696        axis.axhline(y, linestyle='--', color='k')
697        axis.text(label_x, y, kwargs.get("label", ""))
698
699    def _draw_vertical_line_with_label(self, axis: matplotlib.axes.Axes, x: float, **kwargs) -> None:
700        """
701        Draws a vertical line at a given x - coordinate and writes the corresponding label at the edge.
702
703        Parameters
704        ----------
705        axis
706            To draw the vertical line and label on.
707        x
708            Coordinate of the of the line on the x - axis.
709        **kwargs
710            Optional key word arguments for the equilibrium plot.<br>
711            - label: Label for the horizontal line written at the edge.<br>
712            - y: Y - coordinate for vertical thresholds labels (assets of the entrant).<br>
713        """
714        label_y: float = self._get_y_max(kwargs.get("y", 0)) + 0.15
715        axis.axvline(x, linestyle='--', color='k')
716        axis.text(x, label_y, kwargs.get("label", ""))
717
718    def _create_additional_legend(self, options_legend: bool, assets_thresholds_legend: bool, costs_thresholds_legend: bool, width: int) -> str:
719        """
720        Handles the creation of the additional legend for the options of the entrant and incumbent as well as the legend for the thresholds.
721
722        Parameters
723        ----------
724        options_legend: bool
725            States all options of the entrant and the incumbent.
726        assets_thresholds_legend
727            States the thresholds for the assets of the entrant used in the plots.
728        costs_thresholds_legend
729            States the thresholds for the fixed costs of copying of the incumbent used in the plots.
730
731        Returns
732        -------
733        str
734            Containing the legend for the options of the entrant and the incumbent as well as the legend for the thresholds.
735        """
736        legend: str = ""
737        if options_legend:
738            legend += self._create_options_legend(width=width)
739        if assets_thresholds_legend:
740            legend += "\n\n" if options_legend else ""
741            legend += self._create_asset_thresholds_legend(width=width)
742        if costs_thresholds_legend:
743            legend += "\n\n" if options_legend or assets_thresholds_legend else ""
744            legend += self._create_cost_thresholds_legend(width=width)
745        return legend
746
747    def _create_options_legend(self, width: int) -> str:
748        """
749        Creates a legend for the options of the entrant and the incumbent.
750
751        Returns
752        -------
753        str
754            Containing the legend for the options of the entrant and the incumbent.
755        """
756        return "Options of the entrant:\n" + \
757               self._format_legend_line(self.ENTRANT_CHOICES['complement'] + ": Develop an additional complementary product to a primary product.", width=width) + "\n" + \
758               self._format_legend_line(self.ENTRANT_CHOICES['substitute'] + ": Develop an substitute to the primary product of the incumbent.", width=width) + "\n" + \
759               self._format_legend_line(self.ENTRANT_CHOICES['indifferent'] + " : Indifferent between the options mentioned above.", width=width) + "\n" + \
760               "\nOptions of the incumbent:\n" + \
761               self._format_legend_line(self.INCUMBENT_CHOICES['copy'] + " : Copy the original complement of the entrant.", width=width) + "\n" + \
762               self._format_legend_line(self.INCUMBENT_CHOICES['refrain'] + " : Do not copy the original complement of the entrant.", width=width) + "\n" + \
763               "\nOutcomes of the development:\n" + \
764               self._format_legend_line(self.DEVELOPMENT_OUTCOME['success'] + " : The entrant has sufficient assets to develop the product.", width=width) + "\n" + \
765               self._format_legend_line(self.DEVELOPMENT_OUTCOME['failure'] + " : The entrant has not sufficient assets to develop the product.", width=width)
766
767    @staticmethod
768    def _create_asset_thresholds_legend(width: int) -> str:
769        """
770        Creates a legend for the asset of the entrant thresholds used in the plots. The legend is compatible with latex.
771
772        Returns
773        -------
774        str
775             Containing the legend for the thresholds used in the plots.
776        """
777        return "Thresholds for the assets of the entrant:\n" + \
778               BaseModel._format_legend_line(r'$\bar{A}_S$' + ": Minimum level of assets to ensure a perfect substitute gets funded if the incumbent copies.", width=width) + "\n" + \
779               BaseModel._format_legend_line(r'$\bar{A}_S$' + ": Minimum level of assets to ensure a perfect substitute gets funded if the incumbent copies.", width=width) + "\n" + \
780               BaseModel._format_legend_line(r'$\bar{A}_C$' + ": Minimum level of assets to ensure another complement gets funded if the incumbent copies.", width=width) + "\n" + \
781               BaseModel._format_legend_line("If the incumbent does not copy, the entrant will have sufficient assets.", width=width)
782
783    @staticmethod
784    def _create_cost_thresholds_legend(width: int) -> str:
785        """
786        Creates a legend for the thresholds used in the plots. The legend is compatible with latex.
787
788        Returns
789        -------
790        str
791             Containing the legend for the thresholds used in the plots.
792        """
793        return "Thresholds for the fixed costs of copying for the incumbent:\n" + \
794               BaseModel._format_legend_line(r'$F^{YY}_S$' + ": Maximum costs of copying that ensure that the incumbent copies the entrant if the entrant is guaranteed to invest in a perfect substitute.", width=width) + "\n" + \
795               BaseModel._format_legend_line(r'$F^{YN}_S$' + ": Maximum costs of copying that ensure that the incumbent copies the entrant if the copying prevents the entrant from developing a perfect substitute.", width=width) + "\n" + \
796               BaseModel._format_legend_line(r'$F^{YY}_C$' + ": Maximum costs of copying that ensure that the incumbent copies the entrant if the entrant is guaranteed to invest in another complement.", width=width) + "\n" + \
797               BaseModel._format_legend_line(r'$F^{YN}_C$' + ": Maximum costs of copying that ensure that the incumbent copies the entrant if the copying prevents the entrant from developing another complement.", width=width)
798
799    @staticmethod
800    def _format_legend_line(line: str, width: int = 60, latex: bool = True) -> str:
801        space: str = "$\quad$" if latex else " " * 4
802        return textwrap.fill(line, width=width, initial_indent='', subsequent_indent=space * 3)
803
804    @staticmethod
805    def _get_color(i: int) -> str:
806        """
807        Returns a string corresponding to a matplotlib - color for a given index.
808
809        The index helps to get different colors for different items, when iterating over list/dict/etc..
810
811        Parameters
812        ----------
813        i: int
814            Index of the color.
815        Returns
816        -------
817        str
818            A string corresponding to a matplotlib - color for a given index.
819        """
820        return ['salmon', 'khaki', 'limegreen', 'turquoise', 'powderblue', 'thistle', 'pink'][i]
821
822    @staticmethod
823    def _set_axis(axis: matplotlib.axes.Axes) -> None:
824        """
825        Adjusts the axis to the given viewport.
826
827        Parameters
828        ----------
829        axis: matplotlib.axes.Axes
830            To adjust to the given viewport.
831        """
832        axis.autoscale_view()
833        axis.figure.tight_layout()
834
835    @staticmethod
836    def _set_axis_labels(axis: matplotlib.axes.Axes, title: str = "", x_label: str = "", y_label: str = "") -> None:
837        """
838        Sets all the labels for a plot, containing the title, x - label and y - label.
839
840        Parameters
841        ----------
842        axis
843            Axis to set the labels for.
844        title
845            Title of the axis.
846        x_label
847            Label of the x - axis.
848        y_label
849            Label of the y - axis.
850        """
851        axis.set_title(title, loc='left', y=1.1)
852        axis.set_xlabel(x_label)
853        axis.set_ylabel(y_label)
854
855    def __str__(self) -> str:
856        str_representation = self._create_asset_str()
857
858        str_representation += "\n" + self._create_copying_costs_str()
859
860        str_representation += "\n" + self._create_payoff_str()
861
862        return str_representation
863
864    def _create_payoff_str(self):
865        """
866        Creates a string representation for the payoffs of different stakeholder for different market configurations.
867
868        See Shelegia_Motta_2021.IModel.get_payoffs for the formulas of the payoffs.
869
870        Returns
871        -------
872        str
873            String representation for the payoffs of different stakeholder for different market configurations
874        """
875        market_configurations: List[str] = list(self._payoffs.keys())
876        str_representation = 'Payoffs for different Market Configurations:\n\t' + ''.join(
877            ['{0: <14}'.format(item) for item in market_configurations])
878        for utility_type in self._payoffs[market_configurations[0]].keys():
879            str_representation += '\n\t'
880            for market_configuration in market_configurations:
881                str_representation += '-' + '{0: <4}'.format(utility_type).replace('pi', 'π') + ': ' + '{0: <5}'.format(
882                    str(self._payoffs[market_configuration][utility_type])) + '| '
883        return str_representation
884
885    def _create_copying_costs_str(self):
886        """
887        Creates a string representation for the fixed costs of copying for the incumbent.
888
889        See Shelegia_Motta_2021.IModel.get_copying_fixed_costs_values for the formulas of the fixed costs of copying.
890
891        Returns
892        -------
893        str
894            String representation for the fixed costs of copying for the incumbent.
895        """
896        str_representation = 'Costs for copying:'
897        for key in self._copying_fixed_costs.keys():
898            str_representation += '\n\t- ' + key + ':\t' + str(self._copying_fixed_costs[key])
899        return str_representation
900
901    def _create_asset_str(self):
902        """
903        Creates a string representation for the assets of the entrant.
904
905        See Shelegia_Motta_2021.IModel.get_asset_values for the formulas of the assets of the entrant.
906
907        Returns
908        -------
909        str
910            String representation for the assets of the entrant.
911        """
912        str_representation: str = 'Assets:'
913        for key in self._assets:
914            str_representation += '\n\t- ' + key + ':\t' + str(self._assets[key])
915        return str_representation
916
917    def __call__(self, A: float, F: float) -> Dict[str, str]:
918        """
919        Makes the object callable and will return the equilibrium for a given pair of copying fixed costs of the incumbent
920        and assets of the entrant.
921
922        See Shelegia_Motta_2021.IModel.get_optimal_choice for further documentation.
923        """
924        return self.get_optimal_choice(A=A, F=F)

The base model of the project consists of two players: The incumbent, which sells the primary product, and a start-up otherwise known as the entrant which sells a complementary product to the incumbent. One way to visualize a real-world application of this model would be to think of the entrant as a product or service that can be accessed through the platform of the incumbent, like a plug in that can be accessed through Google or a game on Facebook. The aim of this model is to monitor the choice that the entrant has between developing a substitute to or another compliment to the incumbent. The second aim is to observe the choice of the incumbent of whether to copy the original complementary product of the entrant by creating a perfect substitute or not. Seeing as the entrant may not have enough assets to fund a second product, the incumbent copying its first product would inhibit the entrant’s ability to fund its projects. This report will illustrate how the incumbent has a strategic incentive to copy the entrant if it is planning to compete and that it would refrain from copying if the entrant plans to develop a compliment. The subsequent models included in this report will introduce additional factors but will all be based on the basic model.

The equilibrium path arguably supports the “kill zone” argument: due to the risk of an exclusionary strategy by the incumbent, a potential entrant may prefer to avoid a market trajectory which would lead it to compete with the core product of a dominant incumbent and would choose to develop another complementary product instead.

BaseModel( u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51, K: float = 0.2)
44    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
45                 K: float = 0.2) -> None:
46        """
47        Initializes a valid BaseModel object.
48
49        The following preconditions have to be satisfied:
50        - (A1b) $\delta$ / 2 < $\Delta$ < 3 * $\delta$ / 2
51        - (A2) K < $\delta$ / 2
52
53        Parameters
54        ----------
55        u : float
56            Utility gained from consuming the primary product.
57        B : float
58            Minimal difference between the return in case of a success and the return in case of failure of E. B is called the private benefit of the entrant in case of failure.
59        small_delta : float
60            ($\delta$) Additional utility gained from a complement combined with a primary product.
61        delta : float
62            ($\Delta$) Additional utility gained from the substitute of the entrant compared to the primary product of the incumbent.
63        K : float
64            Investment costs for the entrant to develop a second product.
65        """
66        super(BaseModel, self).__init__()
67        assert small_delta / 2 < delta < 3 * small_delta / 2, "(A1b) not satisfied."
68        assert K < small_delta / 2, "(A2) not satisfied."
69        self._u: float = u
70        self._B: float = B
71        self._small_delta: float = small_delta
72        self._delta: float = delta
73        self._K: float = K
74        self._copying_fixed_costs: Dict[str, float] = self._calculate_copying_fixed_costs_values()
75        self._assets: Dict[str, float] = self._calculate_asset_values()
76        self._payoffs: Dict[str, Dict[str, float]] = self._calculate_payoffs()

Initializes a valid BaseModel object.

The following preconditions have to be satisfied:

  • (A1b) $\delta$ / 2 < $\Delta$ < 3 * $\delta$ / 2
  • (A2) K < $\delta$ / 2
Parameters
  • u (float): Utility gained from consuming the primary product.
  • B (float): Minimal difference between the return in case of a success and the return in case of failure of E. B is called the private benefit of the entrant in case of failure.
  • small_delta (float): ($\delta$) Additional utility gained from a complement combined with a primary product.
  • delta (float): ($\Delta$) Additional utility gained from the substitute of the entrant compared to the primary product of the incumbent.
  • K (float): Investment costs for the entrant to develop a second product.
TOLERANCE: Final[float] = 1e-10

Tolerance for the comparison of two floating numbers.

def get_asset_values(self) -> Dict[str, float]:
153    def get_asset_values(self) -> Dict[str, float]:
154        """
155        Returns the asset thresholds of the entrant.
156
157        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
158        |----------------|:----------|:-----------|
159        | $\\underline{A}_S$ | A_s | $(2)\: B + K - \Delta - 3\delta/2$ |
160        | $\\underline{A}_C$ | A_c | $(3)\: B + K - 3\delta/2$ |
161        | $\overline{A}_S$ | A-s | $(4)\: B + K - \Delta$ |
162        | $\overline{A}_C$ | A-c | $(5)\: B + K - \delta/2$ |
163        <br>
164        Returns
165        -------
166        Dict[str, float]
167            Includes the thresholds for the assets of the entrant.
168        """
169        return self._assets

Returns the asset thresholds of the entrant.

Threshold $\:\:\:\:\:$ Name $\:\:\:\:\:$ Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$
$\underline{A}_S$ A_s $(2)\: B + K - \Delta - 3\delta/2$
$\underline{A}_C$ A_c $(3)\: B + K - 3\delta/2$
$\overline{A}_S$ A-s $(4)\: B + K - \Delta$
$\overline{A}_C$ A-c $(5)\: B + K - \delta/2$


Returns
  • Dict[str, float]: Includes the thresholds for the assets of the entrant.
def get_copying_fixed_costs_values(self) -> Dict[str, float]:
171    def get_copying_fixed_costs_values(self) -> Dict[str, float]:
172        """
173        Returns the fixed costs for copying thresholds of the incumbent.
174
175        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
176        |----------|:-------|:--------|
177        | $F^{YY}_S$ | F(YY)s | $(6)\: \delta/2$ |
178        | $F^{YN}_S$ | F(YN)s | $(6)\: u + 3\delta/2$ |
179        | $F^{YY}_C$ | F(YY)c | $(6)\: \delta$ |
180        | $F^{YN}_C$ | F(YN)c | $(6)\: \delta/2$ |
181        <br>
182        Returns
183        -------
184        Dict[str, float]
185            Includes the thresholds for the fixed costs for copying of the incumbent.
186        """
187        return self._copying_fixed_costs

Returns the fixed costs for copying thresholds of the incumbent.

Threshold $\:\:\:\:\:$ Name $\:\:\:\:\:$ Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$
$F^{YY}_S$ F(YY)s $(6)\: \delta/2$
$F^{YN}_S$ F(YN)s $(6)\: u + 3\delta/2$
$F^{YY}_C$ F(YY)c $(6)\: \delta$
$F^{YN}_C$ F(YN)c $(6)\: \delta/2$


Returns
  • Dict[str, float]: Includes the thresholds for the fixed costs for copying of the incumbent.
def get_payoffs(self) -> Dict[str, Dict[str, float]]:
189    def get_payoffs(self) -> Dict[str, Dict[str, float]]:
190        """
191        Returns the payoffs for different market configurations.
192
193        A market configuration can include:
194        - $I_P$ : Primary product sold by the incumbent.
195        - $I_C$ : Complementary product to $I_P$ potentially sold by the incumbent, which is copied from $E_C$.
196        - $E_P$ : Perfect substitute to $I_P$ potentially sold by the entrant.
197        - $E_C$ : Complementary product to $I_P$ currently sold by the entrant
198        - $\\tilde{E}_C$ : Complementary product to $I_P$ potentially sold by the entrant.
199        <br>
200
201        | Market Config. $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(I) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(E) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | CS $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | W $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
202        |-----------------------|:--------|:--------|:--|:-|
203        | $I_P$ ; $E_C$         | $u + \delta/2$ | $\delta/2$ | 0 | $u + \delta$ |
204        | $I_P + I_C$ ; $E_C$   | $u + \delta$ | 0 | 0 | $u + \delta$ |
205        | $I_P$ ; $E_P + E_C$   | 0 | $\Delta + \delta$ | $u$ | $u + \Delta + \delta$ |
206        | $I_P + I_C$ ; $E_P + E_C$ | 0 | $\Delta$ | $u + \delta$ | $u + \Delta + \delta$ |
207        | $I_P$ ; $E_C + \\tilde{E}_C$ | $u + \delta$ | $\delta$ | 0 | $u + 2\delta$ |
208        | $I_P + I_C$ ; $E_C + \\tilde{E}_C$ | $u + 3\delta/2$ | $\delta/2$ | 0 | $u + 2\delta$ |
209        <br>
210
211        Returns
212        -------
213        Dict[str, Dict[str, float]]
214            Contains the mentioned payoffs for different market configurations.
215        """
216        return self._payoffs

Returns the payoffs for different market configurations.

A market configuration can include:

  • $I_P$ : Primary product sold by the incumbent.
  • $I_C$ : Complementary product to $I_P$ potentially sold by the incumbent, which is copied from $E_C$.
  • $E_P$ : Perfect substitute to $I_P$ potentially sold by the entrant.
  • $E_C$ : Complementary product to $I_P$ currently sold by the entrant
  • $\tilde{E}_C$ : Complementary product to $I_P$ potentially sold by the entrant.
Market Config. $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ $\pi(I) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ $\pi(E) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ CS $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ W $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$
$I_P$ ; $E_C$ $u + \delta/2$ $\delta/2$ 0 $u + \delta$
$I_P + I_C$ ; $E_C$ $u + \delta$ 0 0 $u + \delta$
$I_P$ ; $E_P + E_C$ 0 $\Delta + \delta$ $u$ $u + \Delta + \delta$
$I_P + I_C$ ; $E_P + E_C$ 0 $\Delta$ $u + \delta$ $u + \Delta + \delta$
$I_P$ ; $E_C + \tilde{E}_C$ $u + \delta$ $\delta$ 0 $u + 2\delta$
$I_P + I_C$ ; $E_C + \tilde{E}_C$ $u + 3\delta/2$ $\delta/2$ 0 $u + 2\delta$


Returns
  • Dict[str, Dict[str, float]]: Contains the mentioned payoffs for different market configurations.
def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
218    def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
219        result: Dict[str, str] = {"entrant": "", "incumbent": "", "development": ""}
220        if self._copying_fixed_costs["F(YN)c"] <= F <= self._copying_fixed_costs["F(YN)s"] and A < self._assets["A-s"]:
221            result.update({"entrant": self.ENTRANT_CHOICES["complement"]})
222            result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
223            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
224        elif F <= self._copying_fixed_costs["F(YN)c"] and A < self._assets["A-s"]:
225            result.update({"entrant": self.ENTRANT_CHOICES["indifferent"]})
226            result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
227            result.update({"development": self.DEVELOPMENT_OUTCOME["failure"]})
228        else:
229            result.update({"entrant": self.ENTRANT_CHOICES["substitute"]})
230            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
231            if F <= self._copying_fixed_costs["F(YY)s"]:
232                result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
233            else:
234                result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
235        return result

Returns the optimal choice of the entrant and the incumbent based on a pair of assets of the entrant and fixed costs for copying of the incumbent.

The output dictionary will contain the following details:

To understand the details of the logic implemented, consult the chapter in Shelegia and Motta (2021) corresponding to the model.

Parameters
  • A (float): Assets of the entrant.
  • F (float): Fixed costs for copying of the incumbent.
Returns
  • Dict[str, str]: Optimal choice of the entrant, the incumbent and the outcome of the development.
def plot_incumbent_best_answers( self, axis: matplotlib.axes._axes.Axes = None, **kwargs) -> matplotlib.axes._axes.Axes:
289    def plot_incumbent_best_answers(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
290        poly_coordinates: List[List[Tuple[float, float]]] = self._get_incumbent_best_answer_coordinates(
291            kwargs.get("x_max", 0),
292            kwargs.get("y_max", 0))
293        poly_labels: List[str] = self._get_incumbent_best_answer_labels()
294        kwargs.update({'title': kwargs.get('title', "Best Answers of the incumbent to the choices of the entrant")})
295        return self._plot(coordinates=poly_coordinates, labels=poly_labels, axis=axis, **kwargs)

Plots the best answers of the incumbent to all possible actions of the entrant.

Parameters
  • axis (matplotlib.axes.Axes): Axis to draw the plot on. (optional)
  • **kwargs: Optional key word arguments for the best answers plot.
    • title: title on top of the plot, instead of the default title.
    • legend: If false, all legends are turned off.
    • options_legend: If true, an additional legend, explaining the options of the entrant and the incumbent, will be added to the plot.
    • asset_legend: If true, an additional legend explaining the thresholds of the assets of the entrant will be added to the plot.
    • costs_legend: If true, an additional legend explaining the thresholds of the fixed costs of copying for the incumbent will be added to the plot.
    • legend_width : Maximum number of characters in one line in the legend (for adjustments to figure width).
    • x_max : Maximum number plotted on the x - axis.
    • y_max : Maximum number plotted on the y - axis.
Returns
  • matplotlib.axes.Axes: Axis containing the plot.
def plot_equilibrium( self, axis: matplotlib.axes._axes.Axes = None, **kwargs) -> matplotlib.axes._axes.Axes:
397    def plot_equilibrium(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
398        poly_coordinates: List[List[Tuple[float, float]]] = self._get_equilibrium_coordinates(kwargs.get("x_max", 0),
399                                                                                              kwargs.get("y_max", 0))
400        poly_labels: List[str] = self._get_equilibrium_labels()
401        kwargs.update({'title': kwargs.get('title', 'Equilibrium Path')})
402        return self._plot(coordinates=poly_coordinates, labels=poly_labels, axis=axis, **kwargs)

Plots the equilibrium path based on the choices of the entrant and incumbent.

Parameters
  • axis (matplotlib.axes.Axes): Axis to draw the plot on. (optional)
  • **kwargs: Optional key word arguments for the equilibrium plot.
    • title: title on top of the plot, instead of the default title.
    • legend: If false, all legends are turned off.
    • options_legend: If true, an additional legend, explaining the options of the entrant and the incumbent, will be added to the plot.
    • asset_legend: If true, an additional legend explaining the thresholds of the assets of the entrant will be added to the plot.
    • costs_legend: If true, an additional legend explaining the thresholds of the fixed costs of copying for the incumbent will be added to the plot.
    • legend_width : Maximum number of characters in one line in the legend (for adjustments to figure width).
    • x_max : Maximum number plotted on the x - axis.
    • y_max : Maximum number plotted on the y - axis.
Returns
  • matplotlib.axes.Axes: Axis containing the plot.
def plot_payoffs( self, axis: matplotlib.axes._axes.Axes = None, **kwargs) -> matplotlib.axes._axes.Axes:
456    def plot_payoffs(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
457        if axis is None:
458            plot_fig, axis = plt.subplots()
459        index = arange(0, len(self._payoffs) * 2, 2)
460        bar_width = 0.35
461        spacing = 0.05
462
463        self._plot_payoffs_bars(axis, bar_width, index, spacing, **kwargs)
464
465        axis.set_xlabel('Market Configurations')
466        axis.set_title('Payoffs for different Market Configurations')
467        self._set_payoffs_ticks(axis, bar_width, index, spacing)
468        if kwargs.get("legend", True):
469            self._set_payoff_legend(axis, kwargs.get("products_legend", False))
470        self._set_payoffs_figure(axis)
471        return axis

Plots the payoffs for different market configurations.

Parameters
  • axis (matplotlib.axes.Axes): Axis to draw the plot on. (optional)
  • **kwargs: Optional key word arguments for the payoff plot.
    • legend: If false, all legends are turned off.
    • products_legend: If true, a legend, containing all possible products of the entrant and the incumbent, will be added to the plot.
    • opacity : Opacity of the not optimal payoffs. (floating number between 0 and 1)
Returns
  • matplotlib.axes.Axes: Axis containing the plot.
class BargainingPowerModel(BaseModel):
 927class BargainingPowerModel(BaseModel):
 928    """
 929    Besides the parameters used in the paper (and in the BaseModel), this class will introduce the parameter $\\beta$ in the models, called
 930    the bargaining power of the incumbent. $\\beta$ describes how much of the profits from the complementary product of the entrant will go to the incumbent
 931    In the paper the default value $\\beta=0.5$ is used to derive the results, which indicate an equal share of the profits.
 932    """
 933
 934    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
 935                 K: float = 0.2, beta: float = 0.5):
 936        """
 937        Besides $\\beta$ the parameters in this model do not change compared to Shelegia_Motta_2021.Models.BaseModel.
 938
 939        Parameters
 940        ----------
 941        beta: float
 942            Bargaining power of the incumbent relative to the entrant ($0 < \\beta < 1$).
 943        """
 944        assert 0 < beta < 1, 'Invalid bargaining power beta (has to be between 0 and 1).'
 945        self._beta: float = beta
 946        super(BargainingPowerModel, self).__init__(u=u, B=B, small_delta=small_delta, delta=delta, K=K)
 947
 948    def _calculate_payoffs(self) -> Dict[str, Dict[str, float]]:
 949        """
 950        Calculates the payoffs for different market configurations with the formulas given in the paper.
 951
 952        The formulas are tabulated in BargainingPowerModel.get_payoffs, which are different to the BaseModel.
 953
 954        Returns
 955        -------
 956        Dict[str, Dict[str, float]]
 957            Contains the mentioned payoffs for different market configurations.
 958        """
 959        payoffs: Dict[str, Dict[str, float]] = super()._calculate_payoffs()
 960        # basic market.
 961        payoffs['basic']['pi(I)'] = self._u + self._small_delta * self._beta
 962        payoffs['basic']['pi(E)'] = self._small_delta * (1 - self._beta)
 963
 964        # additional complement of the entrant
 965        payoffs['E(C)']['pi(I)'] = self._u + 2 * self._small_delta * self._beta
 966        payoffs['E(C)']['pi(E)'] = 2 * self._small_delta * (1 - self._beta)
 967
 968        # additional complement of the incumbent and the entrant
 969        payoffs['I(C)E(C)']['pi(I)'] = self._u + self._small_delta * (1 + self._beta)
 970        payoffs['I(C)E(C)']['pi(E)'] = self._small_delta * (1 - self._beta)
 971
 972        return payoffs
 973
 974    def _calculate_copying_fixed_costs_values(self) -> Dict[str, float]:
 975        """
 976        Calculates the thresholds for the fixed costs of copying for the incumbent.
 977
 978        The formulas are tabulated in BargainingPowerModel.get_copying_fixed_costs_values, which are different to the BaseModel.
 979
 980        Returns
 981        -------
 982        Dict[str, float]
 983            Includes the thresholds for the fixed costs for copying of the incumbent.
 984        """
 985        return {'F(YY)s': self._small_delta * (1 - self._beta),
 986                'F(YN)s': self._u + self._small_delta * (2 - self._beta),
 987                'F(YY)c': 2 * self._small_delta * (1 - self._beta),
 988                'F(YN)c': self._small_delta * (2 - 3 * self._beta)}
 989
 990    def _calculate_asset_values(self) -> Dict[str, float]:
 991        """
 992        Calculates the thresholds for the assets of the entrant.
 993
 994        The formulas are tabulated in BargainingPowerModel.get_asset_values, which are different to the BaseModel.
 995
 996        Returns
 997        -------
 998        Dict[str, float]
 999            Includes the thresholds for the assets of the entrant.
1000        """
1001        return {'A_s': self._K + self._B - self._delta - self._small_delta * (2 - self._beta),
1002                'A_c': self._K + self._B - 3 * self._small_delta * (1 - self._beta),
1003                'A-s': self._K + self._B - self._delta,
1004                'A-c': self._K + self._B - self._small_delta * (1 - self._beta)}
1005
1006    def get_asset_values(self) -> Dict[str, float]:
1007        """
1008        Returns the asset thresholds of the entrant.
1009
1010        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1011        |----------------|:----------|:-----------|
1012        | $\\underline{A}_S$ | A_s | $B + K - \Delta - \delta(2 - \\beta)$ |
1013        | $\\underline{A}_C$ | A_c | $B + K - 3\delta(1 - \\beta)$ |
1014        | $\overline{A}_S$ | A-s | $B + K - \Delta$ |
1015        | $\overline{A}_C$ | A-c | $B + K - \delta(1 - \\beta)$ |
1016        <br>
1017        Returns
1018        -------
1019        Dict[str, float]
1020            Includes the thresholds for the assets of the entrant.
1021        """
1022        return self._assets
1023
1024    def get_copying_fixed_costs_values(self) -> Dict[str, float]:
1025        """
1026        Returns the fixed costs for copying thresholds of the incumbent.
1027
1028        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1029        |----------|:-------|:--------|
1030        | $F^{YY}_S$ | F(YY)s | $\delta(1 - \\beta)$ |
1031        | $F^{YN}_S$ | F(YN)s | $u + \delta(2 - \\beta)$ |
1032        | $F^{YY}_C$ | F(YY)c | $2\delta(1 - \\beta)$ |
1033        | $F^{YN}_C$ | F(YN)c | $\delta(2 - \\beta)$ |
1034        <br>
1035        Returns
1036        -------
1037        Dict[str, float]
1038            Includes the thresholds for the fixed costs for copying of the incumbent.
1039        """
1040        return self._copying_fixed_costs
1041
1042    def get_payoffs(self) -> Dict[str, Dict[str, float]]:
1043        """
1044        Returns the payoffs for different market configurations.
1045
1046        A market configuration can include:
1047        - $I_P$ : Primary product sold by the incumbent.
1048        - $I_C$ : Complementary product to $I_P$ potentially sold by the incumbent, which is copied from $E_C$.
1049        - $E_P$ : Perfect substitute to $I_P$ potentially sold by the entrant.
1050        - $E_C$ : Complementary product to $I_P$ currently sold by the entrant
1051        - $\\tilde{E}_C$ : Complementary product to $I_P$ potentially sold by the entrant.
1052        <br>
1053
1054        | Market Config. $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(I) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(E) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | CS $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | W $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1055        |-----------------------|:--------|:--------|:--|:-|
1056        | $I_P$ ; $E_C$         | $u + \delta\\beta$ | $\delta(1 - \\beta)$ | 0 | $u + \delta$ |
1057        | $I_P + I_C$ ; $E_C$   | $u + \delta$ | 0 | 0 | $u + \delta$ |
1058        | $I_P$ ; $E_P + E_C$   | 0 | $\Delta + \delta$ | $u$ | $u + \Delta + \delta$ |
1059        | $I_P + I_C$ ; $E_P + E_C$ | 0 | $\Delta$ | $u + \delta$ | $u + \Delta + \delta$ |
1060        | $I_P$ ; $E_C + \\tilde{E}_C$ | $u + 2\delta\\beta$ | $2\delta(1 - \\beta)$ | 0 | $u + 2\delta$ |
1061        | $I_P + I_C$ ; $E_C + \\tilde{E}_C$ | $u + \delta(1 + \\beta)$ | $\delta(1 - \\beta)$ | 0 | $u + 2\delta$ |
1062        <br>
1063
1064        Returns
1065        -------
1066        Dict[str, Dict[str, float]]
1067            Contains the mentioned payoffs for different market configurations.
1068        """
1069        return self._payoffs
1070
1071    def _get_incumbent_best_answer_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
1072        coordinates: List[List[Tuple[float, float]]] = super(BargainingPowerModel,
1073                                                             self)._get_incumbent_best_answer_coordinates(x_max=x_max,
1074                                                                                                          y_max=y_max)
1075        # add additional area 7
1076        if self._copying_fixed_costs["F(YY)s"] != self._copying_fixed_costs["F(YN)c"]:
1077            coordinates.append([(self._assets['A-s'], self._copying_fixed_costs['F(YY)s']),
1078                                (self._assets['A-c'], self._copying_fixed_costs['F(YY)s']),
1079                                (self._assets['A-c'], max(self._copying_fixed_costs['F(YN)c'], 0)),
1080                                (self._assets['A-s'], max(self._copying_fixed_costs['F(YN)c'], 0))])
1081        return coordinates
1082
1083    def _get_incumbent_best_answer_labels(self) -> List[str]:
1084        labels: List[str] = super(BargainingPowerModel, self)._get_incumbent_best_answer_labels()
1085        # add additional label for area 7
1086        if self._copying_fixed_costs["F(YY)s"] != self._copying_fixed_costs["F(YN)c"]:
1087            if self._copying_fixed_costs["F(YY)s"] > self._copying_fixed_costs["F(YN)c"]:
1088                labels.append(
1089                    # Area 7
1090                    self._create_choice_answer_label(entrant="substitute", incumbent="copy",
1091                                                     development="success") + " \n" +
1092                    self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success"),
1093                )
1094            else:
1095                labels.append(
1096                    # Area 7
1097                    self._create_choice_answer_label(entrant="substitute", incumbent="refrain",
1098                                                     development="success") + " \n" +
1099                    self._create_choice_answer_label(entrant="complement", incumbent="copy", development="failure"),
1100                )
1101        return labels

Besides the parameters used in the paper (and in the BaseModel), this class will introduce the parameter $\beta$ in the models, called the bargaining power of the incumbent. $\beta$ describes how much of the profits from the complementary product of the entrant will go to the incumbent In the paper the default value $\beta=0.5$ is used to derive the results, which indicate an equal share of the profits.

BargainingPowerModel( u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51, K: float = 0.2, beta: float = 0.5)
934    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
935                 K: float = 0.2, beta: float = 0.5):
936        """
937        Besides $\\beta$ the parameters in this model do not change compared to Shelegia_Motta_2021.Models.BaseModel.
938
939        Parameters
940        ----------
941        beta: float
942            Bargaining power of the incumbent relative to the entrant ($0 < \\beta < 1$).
943        """
944        assert 0 < beta < 1, 'Invalid bargaining power beta (has to be between 0 and 1).'
945        self._beta: float = beta
946        super(BargainingPowerModel, self).__init__(u=u, B=B, small_delta=small_delta, delta=delta, K=K)

Besides $\beta$ the parameters in this model do not change compared to Shelegia_Motta_2021.Models.BaseModel.

Parameters
  • beta (float): Bargaining power of the incumbent relative to the entrant ($0 < \beta < 1$).
def get_asset_values(self) -> Dict[str, float]:
1006    def get_asset_values(self) -> Dict[str, float]:
1007        """
1008        Returns the asset thresholds of the entrant.
1009
1010        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1011        |----------------|:----------|:-----------|
1012        | $\\underline{A}_S$ | A_s | $B + K - \Delta - \delta(2 - \\beta)$ |
1013        | $\\underline{A}_C$ | A_c | $B + K - 3\delta(1 - \\beta)$ |
1014        | $\overline{A}_S$ | A-s | $B + K - \Delta$ |
1015        | $\overline{A}_C$ | A-c | $B + K - \delta(1 - \\beta)$ |
1016        <br>
1017        Returns
1018        -------
1019        Dict[str, float]
1020            Includes the thresholds for the assets of the entrant.
1021        """
1022        return self._assets

Returns the asset thresholds of the entrant.

Threshold $\:\:\:\:\:$ Name $\:\:\:\:\:$ Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$
$\underline{A}_S$ A_s $B + K - \Delta - \delta(2 - \beta)$
$\underline{A}_C$ A_c $B + K - 3\delta(1 - \beta)$
$\overline{A}_S$ A-s $B + K - \Delta$
$\overline{A}_C$ A-c $B + K - \delta(1 - \beta)$


Returns
  • Dict[str, float]: Includes the thresholds for the assets of the entrant.
def get_copying_fixed_costs_values(self) -> Dict[str, float]:
1024    def get_copying_fixed_costs_values(self) -> Dict[str, float]:
1025        """
1026        Returns the fixed costs for copying thresholds of the incumbent.
1027
1028        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1029        |----------|:-------|:--------|
1030        | $F^{YY}_S$ | F(YY)s | $\delta(1 - \\beta)$ |
1031        | $F^{YN}_S$ | F(YN)s | $u + \delta(2 - \\beta)$ |
1032        | $F^{YY}_C$ | F(YY)c | $2\delta(1 - \\beta)$ |
1033        | $F^{YN}_C$ | F(YN)c | $\delta(2 - \\beta)$ |
1034        <br>
1035        Returns
1036        -------
1037        Dict[str, float]
1038            Includes the thresholds for the fixed costs for copying of the incumbent.
1039        """
1040        return self._copying_fixed_costs

Returns the fixed costs for copying thresholds of the incumbent.

Threshold $\:\:\:\:\:$ Name $\:\:\:\:\:$ Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$
$F^{YY}_S$ F(YY)s $\delta(1 - \beta)$
$F^{YN}_S$ F(YN)s $u + \delta(2 - \beta)$
$F^{YY}_C$ F(YY)c $2\delta(1 - \beta)$
$F^{YN}_C$ F(YN)c $\delta(2 - \beta)$


Returns
  • Dict[str, float]: Includes the thresholds for the fixed costs for copying of the incumbent.
def get_payoffs(self) -> Dict[str, Dict[str, float]]:
1042    def get_payoffs(self) -> Dict[str, Dict[str, float]]:
1043        """
1044        Returns the payoffs for different market configurations.
1045
1046        A market configuration can include:
1047        - $I_P$ : Primary product sold by the incumbent.
1048        - $I_C$ : Complementary product to $I_P$ potentially sold by the incumbent, which is copied from $E_C$.
1049        - $E_P$ : Perfect substitute to $I_P$ potentially sold by the entrant.
1050        - $E_C$ : Complementary product to $I_P$ currently sold by the entrant
1051        - $\\tilde{E}_C$ : Complementary product to $I_P$ potentially sold by the entrant.
1052        <br>
1053
1054        | Market Config. $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(I) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | $\pi(E) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | CS $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ | W $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1055        |-----------------------|:--------|:--------|:--|:-|
1056        | $I_P$ ; $E_C$         | $u + \delta\\beta$ | $\delta(1 - \\beta)$ | 0 | $u + \delta$ |
1057        | $I_P + I_C$ ; $E_C$   | $u + \delta$ | 0 | 0 | $u + \delta$ |
1058        | $I_P$ ; $E_P + E_C$   | 0 | $\Delta + \delta$ | $u$ | $u + \Delta + \delta$ |
1059        | $I_P + I_C$ ; $E_P + E_C$ | 0 | $\Delta$ | $u + \delta$ | $u + \Delta + \delta$ |
1060        | $I_P$ ; $E_C + \\tilde{E}_C$ | $u + 2\delta\\beta$ | $2\delta(1 - \\beta)$ | 0 | $u + 2\delta$ |
1061        | $I_P + I_C$ ; $E_C + \\tilde{E}_C$ | $u + \delta(1 + \\beta)$ | $\delta(1 - \\beta)$ | 0 | $u + 2\delta$ |
1062        <br>
1063
1064        Returns
1065        -------
1066        Dict[str, Dict[str, float]]
1067            Contains the mentioned payoffs for different market configurations.
1068        """
1069        return self._payoffs

Returns the payoffs for different market configurations.

A market configuration can include:

  • $I_P$ : Primary product sold by the incumbent.
  • $I_C$ : Complementary product to $I_P$ potentially sold by the incumbent, which is copied from $E_C$.
  • $E_P$ : Perfect substitute to $I_P$ potentially sold by the entrant.
  • $E_C$ : Complementary product to $I_P$ currently sold by the entrant
  • $\tilde{E}_C$ : Complementary product to $I_P$ potentially sold by the entrant.
Market Config. $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ $\pi(I) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ $\pi(E) \:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ CS $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ W $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$
$I_P$ ; $E_C$ $u + \delta\beta$ $\delta(1 - \beta)$ 0 $u + \delta$
$I_P + I_C$ ; $E_C$ $u + \delta$ 0 0 $u + \delta$
$I_P$ ; $E_P + E_C$ 0 $\Delta + \delta$ $u$ $u + \Delta + \delta$
$I_P + I_C$ ; $E_P + E_C$ 0 $\Delta$ $u + \delta$ $u + \Delta + \delta$
$I_P$ ; $E_C + \tilde{E}_C$ $u + 2\delta\beta$ $2\delta(1 - \beta)$ 0 $u + 2\delta$
$I_P + I_C$ ; $E_C + \tilde{E}_C$ $u + \delta(1 + \beta)$ $\delta(1 - \beta)$ 0 $u + 2\delta$


Returns
  • Dict[str, Dict[str, float]]: Contains the mentioned payoffs for different market configurations.
class UnobservableModel(BargainingPowerModel):
1104class UnobservableModel(BargainingPowerModel):
1105    """
1106    This model indicates that if the incumbent were not able to observe the entrant at the moment of choosing,
1107    the “kill zone” effect whereby the entrant stays away from the substitute in order to avoid being copied would not take place.
1108    Intuitively, in the game as we studied it so far, the only reason why the entrant is choosing a trajectory leading to another complement
1109    is that it anticipates that if it chose one leading to a substitute, the incumbent would copy, making it an inefficient strategy
1110    for entering the market. However, if the incumbent cannot observe the entrant’s choice of strategy, the entrant could not hope to strategically affect the decision
1111    of the incumbent. This would lead to the entrant having a host of new opportunities when entering the market makes the entrant competing with a large company much more attractive.
1112
1113    Although there may be situations where the entrant could commit to some actions (product design or marketing choices)
1114    which signals that it will not become a rival, and it would have all the incentive to commit to do so,
1115    then the game would be like the sequential moves game analyzed in the basic model.
1116    Otherwise, the entrant will never choose a complement just to avoid copying, and it will enter the “kill zone”.
1117    """
1118
1119    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
1120                 K: float = 0.2, beta: float = 0.5):
1121        """
1122        The parameters do not change compared to Shelegia_Motta_2021.Models.BargainingPowerModel.
1123        """
1124        super(UnobservableModel, self).__init__(u=u, B=B, small_delta=small_delta, delta=delta, K=K, beta=beta)
1125
1126    def plot_incumbent_best_answers(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
1127        return self.plot_equilibrium(axis=axis, **kwargs)
1128
1129    def _create_choice_answer_label(self, entrant: Literal["complement", "substitute", "indifferent"],
1130                                    incumbent: Literal["copy", "refrain"],
1131                                    development: Literal["success", "failure"],
1132                                    kill_zone: bool = False,
1133                                    acquisition: str = "") -> str:
1134        return "{" + self.ENTRANT_CHOICES[entrant] + ", " + self.INCUMBENT_CHOICES[incumbent] + "} $\\rightarrow$ " + \
1135               self.DEVELOPMENT_OUTCOME[development]
1136
1137    def _get_equilibrium_labels(self) -> List[str]:
1138        """
1139        Returns a list containing the labels for the squares in the plot of the equilibrium path.
1140
1141        For the order of the squares refer to the file resources/dev_notes.md.
1142
1143        Returns
1144        -------
1145        List containing the labels for the squares in the plot of the best answers of the equilibrium path.
1146        """
1147        return [
1148            # Area 1
1149            self._create_choice_answer_label(entrant="indifferent", incumbent="copy", development="failure"),
1150            # Area 2
1151            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success"),
1152            # Area 3
1153            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="failure"),
1154            # Area 4
1155            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success")
1156        ]
1157
1158    def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
1159        result: Dict = super().get_optimal_choice(A, F)
1160        # adjust the different choices in area three -> since the kill zone does not exist in this model.
1161        if result["entrant"] == self.ENTRANT_CHOICES["complement"]:
1162            result = {"entrant": self.ENTRANT_CHOICES["substitute"], "incumbent": self.INCUMBENT_CHOICES["copy"],
1163                      "development": self.DEVELOPMENT_OUTCOME["failure"]}
1164        return result

This model indicates that if the incumbent were not able to observe the entrant at the moment of choosing, the “kill zone” effect whereby the entrant stays away from the substitute in order to avoid being copied would not take place. Intuitively, in the game as we studied it so far, the only reason why the entrant is choosing a trajectory leading to another complement is that it anticipates that if it chose one leading to a substitute, the incumbent would copy, making it an inefficient strategy for entering the market. However, if the incumbent cannot observe the entrant’s choice of strategy, the entrant could not hope to strategically affect the decision of the incumbent. This would lead to the entrant having a host of new opportunities when entering the market makes the entrant competing with a large company much more attractive.

Although there may be situations where the entrant could commit to some actions (product design or marketing choices) which signals that it will not become a rival, and it would have all the incentive to commit to do so, then the game would be like the sequential moves game analyzed in the basic model. Otherwise, the entrant will never choose a complement just to avoid copying, and it will enter the “kill zone”.

UnobservableModel( u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51, K: float = 0.2, beta: float = 0.5)
1119    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
1120                 K: float = 0.2, beta: float = 0.5):
1121        """
1122        The parameters do not change compared to Shelegia_Motta_2021.Models.BargainingPowerModel.
1123        """
1124        super(UnobservableModel, self).__init__(u=u, B=B, small_delta=small_delta, delta=delta, K=K, beta=beta)

The parameters do not change compared to Shelegia_Motta_2021.Models.BargainingPowerModel.

def plot_incumbent_best_answers( self, axis: matplotlib.axes._axes.Axes = None, **kwargs) -> matplotlib.axes._axes.Axes:
1126    def plot_incumbent_best_answers(self, axis: matplotlib.axes.Axes = None, **kwargs) -> matplotlib.axes.Axes:
1127        return self.plot_equilibrium(axis=axis, **kwargs)

Plots the best answers of the incumbent to all possible actions of the entrant.

Parameters
  • axis (matplotlib.axes.Axes): Axis to draw the plot on. (optional)
  • **kwargs: Optional key word arguments for the best answers plot.
    • title: title on top of the plot, instead of the default title.
    • legend: If false, all legends are turned off.
    • options_legend: If true, an additional legend, explaining the options of the entrant and the incumbent, will be added to the plot.
    • asset_legend: If true, an additional legend explaining the thresholds of the assets of the entrant will be added to the plot.
    • costs_legend: If true, an additional legend explaining the thresholds of the fixed costs of copying for the incumbent will be added to the plot.
    • legend_width : Maximum number of characters in one line in the legend (for adjustments to figure width).
    • x_max : Maximum number plotted on the x - axis.
    • y_max : Maximum number plotted on the y - axis.
Returns
  • matplotlib.axes.Axes: Axis containing the plot.
def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
1158    def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
1159        result: Dict = super().get_optimal_choice(A, F)
1160        # adjust the different choices in area three -> since the kill zone does not exist in this model.
1161        if result["entrant"] == self.ENTRANT_CHOICES["complement"]:
1162            result = {"entrant": self.ENTRANT_CHOICES["substitute"], "incumbent": self.INCUMBENT_CHOICES["copy"],
1163                      "development": self.DEVELOPMENT_OUTCOME["failure"]}
1164        return result

Returns the optimal choice of the entrant and the incumbent based on a pair of assets of the entrant and fixed costs for copying of the incumbent.

The output dictionary will contain the following details:

To understand the details of the logic implemented, consult the chapter in Shelegia and Motta (2021) corresponding to the model.

Parameters
  • A (float): Assets of the entrant.
  • F (float): Fixed costs for copying of the incumbent.
Returns
  • Dict[str, str]: Optimal choice of the entrant, the incumbent and the outcome of the development.
class AcquisitionModel(BargainingPowerModel):
1167class AcquisitionModel(BargainingPowerModel):
1168    """
1169    In order to explore how acquisitions may modify the entrant’s and the incumbent’s strategic choices, we extend the base model
1170    in order to allow an acquisition to take place after the incumbent commits to copying the entrant’s original complementary product
1171    (between t=1 and t=2, see demo.ipynb "Timing of the game"). We assume that the incumbent and the entrant share the gains (if any) attained from the acquisition equally.
1172
1173    The “kill zone” still appears as a possible equilibrium outcome, however for a more reduced region of the parameter space.
1174    The prospect of getting some acquisition gains does tend to increase the profits gained from developing a substitute to the primary product,
1175    and this explains why part of the “kill zone” region where a complement was chosen without the acquisition, the entrant will now choose a substitute instead.
1176    """
1177
1178    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
1179                 K: float = 0.2, beta: float = 0.5) -> None:
1180        """
1181        An additional constraint is added compared to Shelegia_Motta_2021.Models.BaseModel. Namely, $\Delta$ has to be bigger than $u$,
1182        meaning the innovation of the entrant is not too drastic compared with the primary products of the incumbent.
1183
1184        Meanwhile, the parameters do not change compared to Shelegia_Motta_2021.Models.BargainingPowerModel.
1185        """
1186        assert delta < u, "Delta has to be smaller than u, meaning the innovation of the entrant is not too drastic."
1187        super(AcquisitionModel, self).__init__(u=u, B=B, small_delta=small_delta, delta=delta, K=K, beta=beta)
1188        self.ACQUISITION_OUTCOME: Final[Dict[str, str]] = {"merged": "M", "apart": "E"}
1189        """
1190        Contains the options for an acquisition or not.
1191        - merged (M): The incumbent acquired the entrant.
1192        - apart (E): The incumbent did not acquired the entrant.
1193        """
1194
1195    def _calculate_copying_fixed_costs_values(self) -> Dict[str, float]:
1196        copying_fixed_costs_values: Dict[str, float] = super()._calculate_copying_fixed_costs_values()
1197        copying_fixed_costs_values.update(
1198            {'F(ACQ)s': (self._u + self._delta - self._K) / 2 + self._small_delta * (2 - self._beta),
1199             'F(ACQ)c': self._small_delta * (2.5 - 3 * self._beta) - self._K / 2})
1200        assert (abs(copying_fixed_costs_values["F(ACQ)c"] - copying_fixed_costs_values["F(YY)c"]) < self.TOLERANCE or
1201                copying_fixed_costs_values["F(ACQ)c"] < copying_fixed_costs_values["F(YY)c"]), "F(ACQ)c has to be smaller or equal than F(YY)c"
1202        return copying_fixed_costs_values
1203
1204    def get_copying_fixed_costs_values(self) -> Dict[str, float]:
1205        """
1206        Returns the fixed costs for copying thresholds of the incumbent.
1207
1208        Additional thresholds for the fixed cost of copying of the incumbent compared to the Shelegia_Motta_2021.Models.BargainingModel:
1209
1210        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1211        |----------|:-------|:--------|
1212        | $F^{ACQ}_S$ | F(ACQ)s | $\\frac{(u + \Delta - K)}{2} + \delta(2 - \\beta)$ |
1213        | $F^{ACQ}_C$ | F(ACQ)c | $\\frac{K}{2} + \delta(2.5 - 3\\beta)$ |
1214        <br>
1215        As an additional constraint, $F^{ACQ}_C$ has to be smaller or equal than $F^{YY}_C$, since the logic described in the paper may not apply anymore for the other cases.
1216
1217        Returns
1218        -------
1219        Dict[str, float]
1220            Includes the thresholds for the fixed costs for copying of the incumbent.
1221        """
1222        return self._copying_fixed_costs
1223
1224    def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
1225        """
1226        Returns the optimal choice of the entrant and the incumbent based on a pair of assets of the entrant and fixed costs for copying of the incumbent.
1227
1228        The output dictionary will contain the following details:
1229
1230        - "entrant": choice of the entrant (possible choices listed in Shelegia_Motta_2021.IModel.IModel.ENTRANT_CHOICES))
1231        - "incumbent": choice of the incumbent (possible choices listed in Shelegia_Motta_2021.IModel.IModel.INCUMBENT_CHOICES)
1232        - "development": outcome of the development (possible outcomes listed in Shelegia_Motta_2021.IModel.IModel.DEVELOPMENT_OUTCOME)
1233        - "acquisition": outcome of the acquisition (possible outcomes listed in Shelegia_Motta_2021.Models.AcquisitionModel.ACQUISITION_OUTCOME)
1234
1235        To understand the details of the logic implemented, consult the chapter in Shelegia and Motta (2021) corresponding to the model.
1236
1237        Parameters
1238        ----------
1239        A : float
1240            Assets of the entrant.
1241        F : float
1242            Fixed costs for copying of the incumbent.
1243
1244        Returns
1245        -------
1246        Dict[str, str]
1247            Optimal choice of the entrant, the incumbent and the outcome of the development.
1248        """
1249        result: Dict[str, str] = {"entrant": "", "incumbent": "", "development": "", "acquisition": ""}
1250        if self._copying_fixed_costs["F(ACQ)c"] <= F <= self._copying_fixed_costs["F(ACQ)s"] and A < self._assets["A-s"]:
1251            result.update({"entrant": self.ENTRANT_CHOICES["complement"]})
1252            result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
1253            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
1254            result.update({"acquisition": self.ACQUISITION_OUTCOME["apart"]})
1255        elif F < self._copying_fixed_costs["F(ACQ)c"] and A < self._assets["A-s"]:
1256            # to develop a substitute is the weakly dominant strategy of the entrant
1257            entrant_choice_area_1: Literal["substitute", "complement"] = "substitute"
1258            # if the payoff for a complement is higher than for a substitute, the entrant will choose the complement.
1259            if self._delta < self._small_delta:
1260                entrant_choice_area_1 = "complement"
1261            result.update({"entrant": self.ENTRANT_CHOICES[entrant_choice_area_1]})
1262            result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
1263            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
1264            result.update({"acquisition": self.ACQUISITION_OUTCOME["merged"]})
1265        else:
1266            result.update({"entrant": self.ENTRANT_CHOICES["substitute"]})
1267            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
1268            result.update({"acquisition": self.ACQUISITION_OUTCOME["merged"]})
1269            if F <= self._copying_fixed_costs["F(YY)c"]:
1270                result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
1271            else:
1272                result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
1273        return result
1274
1275    def _get_incumbent_best_answer_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
1276        y_max: float = self._get_y_max(y_max)
1277        x_max: float = self._get_x_max(x_max)
1278        return [
1279            # Area 1
1280            [(0, 0), (self._assets['A-c'], 0), (self._assets['A-c'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1281             (0, max(self._copying_fixed_costs['F(ACQ)c'], 0))],
1282            # Area 2
1283            [(self._assets['A-c'], 0), (x_max, 0), (x_max, self._copying_fixed_costs['F(YY)c']),
1284             (self._assets['A-c'], self._copying_fixed_costs['F(YY)c'])],
1285            # Area 3
1286            [(0, max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1287             (self._assets['A-s'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1288             (self._assets['A-s'], self._copying_fixed_costs['F(ACQ)s']), (0, self._copying_fixed_costs['F(ACQ)s'])],
1289            # Area 4
1290            [(self._assets['A-s'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1291             (self._assets['A-c'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1292             (self._assets['A-c'], self._copying_fixed_costs['F(YY)c']),
1293             (self._assets['A-s'], self._copying_fixed_costs['F(YY)c'])],
1294            # Area 5
1295            [(self._assets['A-s'], self._copying_fixed_costs['F(YY)c']), (x_max, self._copying_fixed_costs['F(YY)c']),
1296             (x_max, y_max),
1297             (0, y_max), (0, self._copying_fixed_costs['F(ACQ)s']),
1298             (self._assets['A-s'], self._copying_fixed_costs['F(ACQ)s'])]]
1299
1300    def _get_incumbent_best_answer_labels(self) -> List[str]:
1301        return [
1302            # Area 1
1303            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1304                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1305            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="success",
1306                                             acquisition=self.ACQUISITION_OUTCOME["merged"]),
1307            # Area 2
1308            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1309                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1310            self._create_choice_answer_label(entrant="complement", incumbent="copy", development="success",
1311                                             acquisition=self.ACQUISITION_OUTCOME["apart"]),
1312            # Area 3
1313            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1314                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1315            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
1316                                             acquisition=self.ACQUISITION_OUTCOME["apart"]),
1317            # Area 4
1318            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1319                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1320            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
1321                                             acquisition=self.ACQUISITION_OUTCOME["apart"]),
1322            # Area 5
1323            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success",
1324                                             acquisition=self.ACQUISITION_OUTCOME["merged"]) + " \n" +
1325            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
1326                                             acquisition=self.ACQUISITION_OUTCOME["apart"]),
1327        ]
1328
1329    def _get_equilibrium_coordinates(self, x_max: float, y_max: float) -> List[List[Tuple[float, float]]]:
1330        y_max: float = self._get_y_max(y_max)
1331        x_max: float = self._get_x_max(x_max)
1332        return [
1333            # Area 1
1334            [(0, 0), (self._assets['A-s'], 0), (self._assets['A-s'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1335             (0, max(self._copying_fixed_costs['F(ACQ)c'], 0))],
1336            # Area 2
1337            [(self._assets['A-s'], 0), (x_max, 0), (x_max, self._copying_fixed_costs['F(YY)c']),
1338             (self._assets['A-s'], self._copying_fixed_costs['F(YY)c'])],
1339            # Area 3
1340            [(0, max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1341             (self._assets['A-s'], max(self._copying_fixed_costs['F(ACQ)c'], 0)),
1342             (self._assets['A-s'], self._copying_fixed_costs['F(ACQ)s']), (0, self._copying_fixed_costs['F(ACQ)s'])],
1343            # Area 4
1344            [(self._assets['A-s'], self._copying_fixed_costs['F(YY)c']), (x_max, self._copying_fixed_costs['F(YY)c']),
1345             (x_max, y_max), (0, y_max), (0, self._copying_fixed_costs['F(ACQ)s']),
1346             (self._assets['A-s'], self._copying_fixed_costs['F(ACQ)s'])]]
1347
1348    def _get_equilibrium_labels(self) -> List[str]:
1349        # to develop a substitute is the weakly dominant strategy of the entrant
1350        entrant_choice_area_1: Literal["substitute", "complement"] = "substitute"
1351        # if the payoff for a complement is higher than for a substitute, the entrant will choose the complement.
1352        if self._delta < self._small_delta:
1353            entrant_choice_area_1 = "complement"
1354        return [
1355            # Area 1
1356            self._create_choice_answer_label(entrant=entrant_choice_area_1, incumbent="copy", development="success",
1357                                             acquisition=self.ACQUISITION_OUTCOME["merged"]),
1358            # Area 2
1359            self._create_choice_answer_label(entrant="substitute", incumbent="copy", development="success",
1360                                             acquisition=self.ACQUISITION_OUTCOME["merged"]),
1361            # Area 3
1362            self._create_choice_answer_label(entrant="complement", incumbent="refrain", development="success",
1363                                             kill_zone=True, acquisition=self.ACQUISITION_OUTCOME["apart"]),
1364            # Area 4
1365            self._create_choice_answer_label(entrant="substitute", incumbent="refrain", development="success",
1366                                             acquisition=self.ACQUISITION_OUTCOME["merged"])
1367        ]
1368
1369    def _draw_thresholds(self, axis: matplotlib.axes.Axes, x_horizontal: float = 0, y_vertical: float = 0) -> None:
1370        self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(ACQ)s'], label="$F^{ACQ}_S$",
1371                                              x=x_horizontal)
1372        self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YN)s'], label="$F^{YN}_S$",
1373                                              x=x_horizontal)
1374
1375        if abs(self._copying_fixed_costs['F(YY)c'] - self._copying_fixed_costs['F(ACQ)c']) < self.TOLERANCE:
1376            self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(ACQ)c'], x=x_horizontal,
1377                                                  label="$F^{ACQ}_C=F^{YY}_C$")
1378        else:
1379            if self._copying_fixed_costs['F(ACQ)c'] >= 0:
1380                self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(ACQ)c'], x=x_horizontal,
1381                                                      label="$F^{ACQ}_C$")
1382            self._draw_horizontal_line_with_label(axis, y=self._copying_fixed_costs['F(YY)c'], label="$F^{YY}_C$",
1383                                                      x=x_horizontal)
1384        # vertical lines (asset thresholds)
1385        self._draw_vertical_line_with_label(axis, x=self._assets['A-s'], label=r'$\bar{A}_S$', y=y_vertical)
1386        self._draw_vertical_line_with_label(axis, x=self._assets['A-c'], label=r'$\bar{A}_C$', y=y_vertical)
1387
1388    @staticmethod
1389    def _create_cost_thresholds_legend(width: int) -> str:
1390        legend: str = super(AcquisitionModel, AcquisitionModel)._create_cost_thresholds_legend(width=width)
1391        return legend + "\n" + \
1392               AcquisitionModel._format_legend_line(r'$F^{ACQ}_C$' + ": Maximum level of fixed costs that ensure that the incumbent acquires the entrant if the entrant develops a second complement.", width=width) + "\n" + \
1393               AcquisitionModel._format_legend_line(r'$F^{ACQ}_S$' + ": Maximum level of fixed costs that ensure that the incumbent acquires the entrant if the entrant develops a perfect substitute.", width=width)
1394
1395    def _create_options_legend(self, width: int) -> str:
1396        legend: str = super(AcquisitionModel, self)._create_options_legend(width=width)
1397        # modify outcomes without acquisition
1398        legend = legend.replace(self.DEVELOPMENT_OUTCOME['success'], "$" + self.DEVELOPMENT_OUTCOME['success'] + "_" + self.ACQUISITION_OUTCOME["apart"] + "$")
1399        legend = legend.replace(self.DEVELOPMENT_OUTCOME['failure'], "$" + self.DEVELOPMENT_OUTCOME['failure'] + "_" + self.ACQUISITION_OUTCOME["apart"] + "$")
1400
1401        # add additional outcomes with acquisition
1402        return legend + "\n" + \
1403               self._format_legend_line("$" + self.DEVELOPMENT_OUTCOME['success'] + "_" + self.ACQUISITION_OUTCOME["merged"] + "$ : The merged entity has sufficient assets to develop the product.", width=width) + "\n" + \
1404               self._format_legend_line("$" + self.DEVELOPMENT_OUTCOME['failure'] + "_" + self.ACQUISITION_OUTCOME["merged"] + "$ : The merged entity has not sufficient assets to develop the product.", width=width)

In order to explore how acquisitions may modify the entrant’s and the incumbent’s strategic choices, we extend the base model in order to allow an acquisition to take place after the incumbent commits to copying the entrant’s original complementary product (between t=1 and t=2, see demo.ipynb "Timing of the game"). We assume that the incumbent and the entrant share the gains (if any) attained from the acquisition equally.

The “kill zone” still appears as a possible equilibrium outcome, however for a more reduced region of the parameter space. The prospect of getting some acquisition gains does tend to increase the profits gained from developing a substitute to the primary product, and this explains why part of the “kill zone” region where a complement was chosen without the acquisition, the entrant will now choose a substitute instead.

AcquisitionModel( u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51, K: float = 0.2, beta: float = 0.5)
1178    def __init__(self, u: float = 1, B: float = 0.5, small_delta: float = 0.5, delta: float = 0.51,
1179                 K: float = 0.2, beta: float = 0.5) -> None:
1180        """
1181        An additional constraint is added compared to Shelegia_Motta_2021.Models.BaseModel. Namely, $\Delta$ has to be bigger than $u$,
1182        meaning the innovation of the entrant is not too drastic compared with the primary products of the incumbent.
1183
1184        Meanwhile, the parameters do not change compared to Shelegia_Motta_2021.Models.BargainingPowerModel.
1185        """
1186        assert delta < u, "Delta has to be smaller than u, meaning the innovation of the entrant is not too drastic."
1187        super(AcquisitionModel, self).__init__(u=u, B=B, small_delta=small_delta, delta=delta, K=K, beta=beta)
1188        self.ACQUISITION_OUTCOME: Final[Dict[str, str]] = {"merged": "M", "apart": "E"}
1189        """
1190        Contains the options for an acquisition or not.
1191        - merged (M): The incumbent acquired the entrant.
1192        - apart (E): The incumbent did not acquired the entrant.
1193        """

An additional constraint is added compared to Shelegia_Motta_2021.Models.BaseModel. Namely, $\Delta$ has to be bigger than $u$, meaning the innovation of the entrant is not too drastic compared with the primary products of the incumbent.

Meanwhile, the parameters do not change compared to Shelegia_Motta_2021.Models.BargainingPowerModel.

ACQUISITION_OUTCOME: Final[Dict[str, str]]

Contains the options for an acquisition or not.

  • merged (M): The incumbent acquired the entrant.
  • apart (E): The incumbent did not acquired the entrant.
def get_copying_fixed_costs_values(self) -> Dict[str, float]:
1204    def get_copying_fixed_costs_values(self) -> Dict[str, float]:
1205        """
1206        Returns the fixed costs for copying thresholds of the incumbent.
1207
1208        Additional thresholds for the fixed cost of copying of the incumbent compared to the Shelegia_Motta_2021.Models.BargainingModel:
1209
1210        | Threshold $\:\:\:\:\:$ | Name $\:\:\:\:\:$ | Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$ |
1211        |----------|:-------|:--------|
1212        | $F^{ACQ}_S$ | F(ACQ)s | $\\frac{(u + \Delta - K)}{2} + \delta(2 - \\beta)$ |
1213        | $F^{ACQ}_C$ | F(ACQ)c | $\\frac{K}{2} + \delta(2.5 - 3\\beta)$ |
1214        <br>
1215        As an additional constraint, $F^{ACQ}_C$ has to be smaller or equal than $F^{YY}_C$, since the logic described in the paper may not apply anymore for the other cases.
1216
1217        Returns
1218        -------
1219        Dict[str, float]
1220            Includes the thresholds for the fixed costs for copying of the incumbent.
1221        """
1222        return self._copying_fixed_costs

Returns the fixed costs for copying thresholds of the incumbent.

Additional thresholds for the fixed cost of copying of the incumbent compared to the Shelegia_Motta_2021.Models.BargainingModel:

Threshold $\:\:\:\:\:$ Name $\:\:\:\:\:$ Formula $\:\:\:\:\:\:\:\:\:\:\:\:\:\:\:$
$F^{ACQ}_S$ F(ACQ)s $\frac{(u + \Delta - K)}{2} + \delta(2 - \beta)$
$F^{ACQ}_C$ F(ACQ)c $\frac{K}{2} + \delta(2.5 - 3\beta)$


As an additional constraint, $F^{ACQ}_C$ has to be smaller or equal than $F^{YY}_C$, since the logic described in the paper may not apply anymore for the other cases.

Returns
  • Dict[str, float]: Includes the thresholds for the fixed costs for copying of the incumbent.
def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
1224    def get_optimal_choice(self, A: float, F: float) -> Dict[str, str]:
1225        """
1226        Returns the optimal choice of the entrant and the incumbent based on a pair of assets of the entrant and fixed costs for copying of the incumbent.
1227
1228        The output dictionary will contain the following details:
1229
1230        - "entrant": choice of the entrant (possible choices listed in Shelegia_Motta_2021.IModel.IModel.ENTRANT_CHOICES))
1231        - "incumbent": choice of the incumbent (possible choices listed in Shelegia_Motta_2021.IModel.IModel.INCUMBENT_CHOICES)
1232        - "development": outcome of the development (possible outcomes listed in Shelegia_Motta_2021.IModel.IModel.DEVELOPMENT_OUTCOME)
1233        - "acquisition": outcome of the acquisition (possible outcomes listed in Shelegia_Motta_2021.Models.AcquisitionModel.ACQUISITION_OUTCOME)
1234
1235        To understand the details of the logic implemented, consult the chapter in Shelegia and Motta (2021) corresponding to the model.
1236
1237        Parameters
1238        ----------
1239        A : float
1240            Assets of the entrant.
1241        F : float
1242            Fixed costs for copying of the incumbent.
1243
1244        Returns
1245        -------
1246        Dict[str, str]
1247            Optimal choice of the entrant, the incumbent and the outcome of the development.
1248        """
1249        result: Dict[str, str] = {"entrant": "", "incumbent": "", "development": "", "acquisition": ""}
1250        if self._copying_fixed_costs["F(ACQ)c"] <= F <= self._copying_fixed_costs["F(ACQ)s"] and A < self._assets["A-s"]:
1251            result.update({"entrant": self.ENTRANT_CHOICES["complement"]})
1252            result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
1253            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
1254            result.update({"acquisition": self.ACQUISITION_OUTCOME["apart"]})
1255        elif F < self._copying_fixed_costs["F(ACQ)c"] and A < self._assets["A-s"]:
1256            # to develop a substitute is the weakly dominant strategy of the entrant
1257            entrant_choice_area_1: Literal["substitute", "complement"] = "substitute"
1258            # if the payoff for a complement is higher than for a substitute, the entrant will choose the complement.
1259            if self._delta < self._small_delta:
1260                entrant_choice_area_1 = "complement"
1261            result.update({"entrant": self.ENTRANT_CHOICES[entrant_choice_area_1]})
1262            result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
1263            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
1264            result.update({"acquisition": self.ACQUISITION_OUTCOME["merged"]})
1265        else:
1266            result.update({"entrant": self.ENTRANT_CHOICES["substitute"]})
1267            result.update({"development": self.DEVELOPMENT_OUTCOME["success"]})
1268            result.update({"acquisition": self.ACQUISITION_OUTCOME["merged"]})
1269            if F <= self._copying_fixed_costs["F(YY)c"]:
1270                result.update({"incumbent": self.INCUMBENT_CHOICES["copy"]})
1271            else:
1272                result.update({"incumbent": self.INCUMBENT_CHOICES["refrain"]})
1273        return result

Returns the optimal choice of the entrant and the incumbent based on a pair of assets of the entrant and fixed costs for copying of the incumbent.

The output dictionary will contain the following details:

To understand the details of the logic implemented, consult the chapter in Shelegia and Motta (2021) corresponding to the model.

Parameters
  • A (float): Assets of the entrant.
  • F (float): Fixed costs for copying of the incumbent.
Returns
  • Dict[str, str]: Optimal choice of the entrant, the incumbent and the outcome of the development.