diff --git a/examples/utility_function_demo.ipynb b/examples/utility_function_demo.ipynb new file mode 100644 index 0000000..ee54102 --- /dev/null +++ b/examples/utility_function_demo.ipynb @@ -0,0 +1,607 @@ +{ + "nbformat": 4, + "nbformat_minor": 0, + "metadata": { + "colab": { + "provenance": [], + "authorship_tag": "ABX9TyMLWlCaXa90pQB7POCVUrPr", + "include_colab_link": true + }, + "kernelspec": { + "name": "python3", + "display_name": "Python 3" + }, + "language_info": { + "name": "python" + } + }, + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "view-in-github", + "colab_type": "text" + }, + "source": [ + "\"Open" + ] + }, + { + "cell_type": "markdown", + "source": [ + "This notebook demonstrates three unique ways to compute the utility of a vehicle. The outcomes from each case may or may not align! The three approaches are:\n", + "1. Partworth utility over a measure of quality for every unique component of the car\n", + "2. Quadratic utility over a measure of quality for every unique component of the car\n", + "2. Quadratic utility over the performance attributes of the car, each of which relies on several components" + ], + "metadata": { + "id": "NdwyMZ8Pn_kB" + } + }, + { + "cell_type": "markdown", + "source": [ + "## Install and Import" + ], + "metadata": { + "id": "HAJrSacGn5vz" + } + }, + { + "cell_type": "code", + "execution_count": 1, + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8f9JU38Feqxv", + "outputId": "6e758127-832d-4ca5-d675-ff04f3d8abbd" + }, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "Collecting git+https://github.com/cmudrc/sae.git\n", + " Cloning https://github.com/cmudrc/sae.git to /tmp/pip-req-build-m8fb5j5i\n", + " Running command git clone --filter=blob:none --quiet https://github.com/cmudrc/sae.git /tmp/pip-req-build-m8fb5j5i\n", + " Resolved https://github.com/cmudrc/sae.git to commit ce6a789e0b810785ba4fb570dcbea5b40848508f\n", + " Preparing metadata (setup.py) ... \u001b[?25l\u001b[?25hdone\n", + "Requirement already satisfied: numpy in /usr/local/lib/python3.10/dist-packages (from sae==0.1.0) (1.23.5)\n", + "Requirement already satisfied: scipy in /usr/local/lib/python3.10/dist-packages (from sae==0.1.0) (1.11.4)\n", + "Requirement already satisfied: pandas in /usr/local/lib/python3.10/dist-packages (from sae==0.1.0) (1.5.3)\n", + "Requirement already satisfied: openpyxl in /usr/local/lib/python3.10/dist-packages (from sae==0.1.0) (3.1.2)\n", + "Requirement already satisfied: et-xmlfile in /usr/local/lib/python3.10/dist-packages (from openpyxl->sae==0.1.0) (1.1.0)\n", + "Requirement already satisfied: python-dateutil>=2.8.1 in /usr/local/lib/python3.10/dist-packages (from pandas->sae==0.1.0) (2.8.2)\n", + "Requirement already satisfied: pytz>=2020.1 in /usr/local/lib/python3.10/dist-packages (from pandas->sae==0.1.0) (2023.3.post1)\n", + "Requirement already satisfied: six>=1.5 in /usr/local/lib/python3.10/dist-packages (from python-dateutil>=2.8.1->pandas->sae==0.1.0) (1.16.0)\n", + "Building wheels for collected packages: sae\n", + " Building wheel for sae (setup.py) ... \u001b[?25l\u001b[?25hdone\n", + " Created wheel for sae: filename=sae-0.1.0-py3-none-any.whl size=26146 sha256=c875573c5df75f9da76c8c09fd28182801e67aa8f7732e387398a756caff1d82\n", + " Stored in directory: /tmp/pip-ephem-wheel-cache-5rgx4t57/wheels/8d/20/04/8e9bbfdfb118386e3253608654c865b42b674862a738800cfb\n", + "Successfully built sae\n", + "Installing collected packages: sae\n", + "Successfully installed sae-0.1.0\n" + ] + } + ], + "source": [ + "!pip install git+https://github.com/cmudrc/sae.git" + ] + }, + { + "cell_type": "code", + "source": [ + "import numpy\n", + "from numpy.typing import NDArray\n", + "from typing import Tuple\n", + "import sae" + ], + "metadata": { + "id": "io0PLTfGe9b2" + }, + "execution_count": 10, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "## Generate two random cars for us to examine" + ], + "metadata": { + "id": "G0M5Q8F3nzbd" + } + }, + { + "cell_type": "code", + "source": [ + "example_car_1 = sae.Car()\n", + "example_car_2 = sae.Car()" + ], + "metadata": { + "id": "RtEDRw4qetSW" + }, + "execution_count": 3, + "outputs": [] + }, + { + "cell_type": "markdown", + "source": [ + "# Simulating Random Customers with Partworth Utility" + ], + "metadata": { + "id": "9U4Bk3q9hL-m" + } + }, + { + "cell_type": "code", + "source": [ + "# Get weights for a random customer that define their preferences\n", + "weights = numpy.random.rand(10)\n", + "print(weights)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "Vb3JO1FZfms4", + "outputId": "4834db10-89b2-450c-8b33-d1cb34629274" + }, + "execution_count": 4, + "outputs": [ + { + "output_type": "stream", + "name": "stdout", + "text": [ + "[0.80796276 0.22177387 0.8828941 0.38631095 0.90739784 0.5546031\n", + " 0.83253125 0.36430607 0.6914742 0.10234462]\n" + ] + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Evaluate the car with respect to those weights. Note that the returned values\n", + "# are scaled according to approximate bounds and represent utility*-1 to provide\n", + "# an implicit minimization framing\n", + "example_car_1.partworth_objectives(weights=weights)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "_mwPC6Bqgq37", + "outputId": "448a2056-c491-4c8d-9fff-bdef572f1f7d" + }, + "execution_count": 5, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "(-1.0028852531019703,\n", + " array([-0.00828908, -0.19115229, -0.00247374, -0.01126424, -0.03145214,\n", + " -0.41163063, -0.3281428 , -0.3718878 , -0.28225456, -0.84603138]))" + ] + }, + "metadata": {}, + "execution_count": 5 + } + ] + }, + { + "cell_type": "code", + "source": [ + "example_car_2.partworth_objectives(weights=weights)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "LFzk52iPiK9y", + "outputId": "f3ebd06b-733f-4f3d-97a2-acc1941e560b" + }, + "execution_count": 6, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "(-1.234540242718953,\n", + " array([-0.77745905, -0.02698055, -0.1038017 , -0.01007241, -0.00431661,\n", + " -0.14338959, -0.24939046, -0.28994642, -0.10844283, -0.32420369]))" + ] + }, + "metadata": {}, + "execution_count": 6 + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Assuming the customer acts rationally with respect to this preference model,\n", + "# they should buy the following car:\n", + "\"Car 1\" if example_car_1.partworth_objectives(weights=weights)[0] < example_car_2.partworth_objectives(weights=weights)[0] else \"Car 2\"" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 35 + }, + "id": "2oeLDHcWilHv", + "outputId": "6537eb9a-5631-4c53-e0bc-0d71aae84bb2" + }, + "execution_count": 7, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "'Car 2'" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "string" + } + }, + "metadata": {}, + "execution_count": 7 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Simulating Random Customers with Quadratic Utility over Parts" + ], + "metadata": { + "id": "gj1X6sp1jr_N" + } + }, + { + "cell_type": "code", + "source": [ + "# The point of simulating customers with more complex utility functions is that\n", + "# sometimes more isn't necessarily better! For this" + ], + "metadata": { + "id": "cCm85pRGjW3C" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def quadratic_utility_over_parts(car: sae.Car, weights: NDArray) -> Tuple[float, list]:\n", + " partworth = -car.partworth_objectives()[1]\n", + " quadratic_partworth = []\n", + " quadratic_utility = 0.0\n", + " for idx, part in enumerate(list(partworth)):\n", + " quadratic_part = weights[idx,0] + weights[idx,1]*part + weights[idx,2]*part*part\n", + " quadratic_utility -= quadratic_part\n", + " quadratic_partworth.append(-quadratic_part)\n", + "\n", + " return (quadratic_utility, quadratic_partworth)" + ], + "metadata": { + "id": "Px2NqKz4kJaZ" + }, + "execution_count": 23, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Define the mean form for our weights - this should yield a \"leveling out\" as\n", + "# parts become more performant\n", + "mean = numpy.array([[0.0, 2.0, -1.0]] * 10)" + ], + "metadata": { + "id": "wYr9zyXymW2P" + }, + "execution_count": 24, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# We can now add some variance to each of the weights\n", + "weights = mean + 0.1*numpy.random.randn(10, 3)" + ], + "metadata": { + "id": "AnrK7nRanAL4" + }, + "execution_count": 26, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# With these new weights, we can compute the utility\n", + "quadratic_utility_over_parts(example_car_1, weights)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "r5iBMkCrmdkz", + "outputId": "eeb631e0-016d-4686-a496-eb01cb89d0e2" + }, + "execution_count": 30, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "(-4.071488205948333,\n", + " [-0.03126401575296759,\n", + " -0.49089609191384587,\n", + " 0.10999728521578471,\n", + " -0.1251560690047339,\n", + " -0.23026173716869616,\n", + " -0.8025663995233542,\n", + " -0.6015243863484405,\n", + " -0.6218019671511774,\n", + " -0.4599948061092836,\n", + " -0.8180200181916184])" + ] + }, + "metadata": {}, + "execution_count": 30 + } + ] + }, + { + "cell_type": "code", + "source": [ + "quadratic_utility_over_parts(example_car_2, weights)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "foj8zMwCnW7I", + "outputId": "05b55459-3bcc-4bc4-aa56-840a8f23d83d" + }, + "execution_count": 34, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "(-3.5374681841143047,\n", + " [-0.9674580251962474,\n", + " -0.17123561612296972,\n", + " -0.091656557801661,\n", + " -0.1227515219969897,\n", + " -0.1722880595563746,\n", + " -0.3619720050640406,\n", + " -0.47527137316762497,\n", + " -0.5237436374864741,\n", + " -0.17106735660811145,\n", + " -0.4800240311138118])" + ] + }, + "metadata": {}, + "execution_count": 34 + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Assuming the customer acts rationally with respect to this preference model,\n", + "# they should buy the following car:\n", + "\"Car 1\" if quadratic_utility_over_parts(example_car_1, weights)[0] < quadratic_utility_over_parts(example_car_2, weights)[0] else \"Car 2\"" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 35 + }, + "id": "nglaLu6SmzNn", + "outputId": "6716923c-847e-4ef0-d23e-155e42159ced" + }, + "execution_count": 32, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "'Car 1'" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "string" + } + }, + "metadata": {}, + "execution_count": 32 + } + ] + }, + { + "cell_type": "markdown", + "source": [ + "# Simulating Random Customers with Quadratic Utility over Performance" + ], + "metadata": { + "id": "KaWYPF9fnaq0" + } + }, + { + "cell_type": "code", + "source": [ + "# The point of simulating customers with more complex utility functions is that\n", + "# sometimes more isn't necessarily better!" + ], + "metadata": { + "id": "h_OIoZ-Enaq1" + }, + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "def quadratic_utility_over_performance(car: sae.Car, weights: NDArray) -> Tuple[float, list]:\n", + " partworth = car.objectives()[1]\n", + " quadratic_partworth = []\n", + " quadratic_utility = 0.0\n", + " for idx, part in enumerate(list(partworth)):\n", + " quadratic_part = weights[idx,0] + weights[idx,1]*part + weights[idx,2]*part*part\n", + " quadratic_utility -= quadratic_part\n", + " quadratic_partworth.append(-quadratic_part)\n", + "\n", + " return (quadratic_utility, quadratic_partworth)" + ], + "metadata": { + "id": "ToqHACc5naq1" + }, + "execution_count": 42, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# Define the mean form for our weights - this should yield a \"leveling out\" as\n", + "# parts become more performant\n", + "mean = numpy.array([[0.0, 2.0, -1.0]] * 11)" + ], + "metadata": { + "id": "FFNf9K4Enaq1" + }, + "execution_count": 43, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# We can now add some variance to each of the weights\n", + "weights = mean + 0.1*numpy.random.randn(11, 3)" + ], + "metadata": { + "id": "LOVcbDAInaq1" + }, + "execution_count": 44, + "outputs": [] + }, + { + "cell_type": "code", + "source": [ + "# With these new weights, we can compute the utility\n", + "quadratic_utility_over_performance(example_car_1, weights)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "bc823076-4c76-4b93-abee-751324bbe014", + "id": "lKGb3qSonaq1" + }, + "execution_count": 45, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "(-4.903078439305462,\n", + " [0.0981353783732669,\n", + " -0.2994749708779486,\n", + " -0.20648371506368626,\n", + " -1.0953602066268822,\n", + " -1.0351736151108994,\n", + " -0.19673768920421758,\n", + " -0.09611383594573847,\n", + " -1.25267105164887,\n", + " -0.14563029029530491,\n", + " -0.1956765490085978,\n", + " -0.4778918938965845])" + ] + }, + "metadata": {}, + "execution_count": 45 + } + ] + }, + { + "cell_type": "code", + "source": [ + "quadratic_utility_over_performance(example_car_2, weights)" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "outputId": "2c9a4c41-d8f9-44e0-e8a4-9449173ecd6c", + "id": "tT8UiE2hnaq2" + }, + "execution_count": 46, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "(-6.705037303255647,\n", + " [-0.0224891939112073,\n", + " -0.8982419122961699,\n", + " -0.7106703330066542,\n", + " -0.7165058036911959,\n", + " -1.034878063603879,\n", + " -0.3989786308510101,\n", + " -0.3858572049689103,\n", + " -0.9292412429505923,\n", + " -0.30438664934800563,\n", + " -0.5783186742202403,\n", + " -0.7254695944077834])" + ] + }, + "metadata": {}, + "execution_count": 46 + } + ] + }, + { + "cell_type": "code", + "source": [ + "# Assuming the customer acts rationally with respect to this preference model,\n", + "# they should buy the following car:\n", + "\"Car 1\" if quadratic_utility_over_parts(example_car_1, weights)[0] < quadratic_utility_over_parts(example_car_2, weights)[0] else \"Car 2\"" + ], + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 35 + }, + "outputId": "c621c7dc-6059-42be-b064-c2edbbb91ca3", + "id": "lIpu96W1naq2" + }, + "execution_count": 47, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "'Car 1'" + ], + "application/vnd.google.colaboratory.intrinsic+json": { + "type": "string" + } + }, + "metadata": {}, + "execution_count": 47 + } + ] + }, + { + "cell_type": "code", + "source": [], + "metadata": { + "id": "d9hukZEmntiJ" + }, + "execution_count": null, + "outputs": [] + } + ] +} \ No newline at end of file