You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
547 lines
18 KiB
Plaintext
547 lines
18 KiB
Plaintext
{
|
|
"cells": [
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 1,
|
|
"id": "indie-evolution",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
"import torch\n",
|
|
"from torch.autograd.functional import jacobian\n",
|
|
"import itertools\n",
|
|
"import math\n",
|
|
"\n",
|
|
"class States():\n",
|
|
" \"\"\"\n",
|
|
" This is supposed to capture the state variables of the model\n",
|
|
" \"\"\"\n",
|
|
" def __init__(self, stocks,debris):\n",
|
|
" pass\n",
|
|
"\n",
|
|
" def transition(self):\n",
|
|
" #tying the transition functions in here might be useful.\n",
|
|
" pass\n",
|
|
"\n",
|
|
" \n",
|
|
"class LDAPV():\n",
|
|
" \"\"\"\n",
|
|
" This class represents the outputs from the neural network.\n",
|
|
" They are of two general categories:\n",
|
|
" - the launch functions \n",
|
|
" \"\"\"\n",
|
|
" def __init__(self, launches, partials):\n",
|
|
" self.launches = launches\n",
|
|
" self.partials = partials\n",
|
|
" \n",
|
|
" def launch_single(constellation):\n",
|
|
" #returns the launch decision for the constellation of interest\n",
|
|
" \n",
|
|
" filter_tensor = torch.zeros(NUMBER_CONSTELLATIONS)\n",
|
|
" filter_tensor[constellation] = 1.0\n",
|
|
" \n",
|
|
" return self.launches @ filter_tensor\n",
|
|
" \n",
|
|
" def launch_vector(constellation):\n",
|
|
" #returns the launch decision for the constellation of interest\n",
|
|
" \n",
|
|
" filter_tensor = torch.zeros(NUMBER_CONSTELLATIONS)\n",
|
|
" filter_tensor[constellation] = 1.0\n",
|
|
" \n",
|
|
" return self.launches * filter_tensor\n",
|
|
" \n",
|
|
" def partial_vector(constellation):\n",
|
|
" #returns the partials of the value function corresponding to the constellation of interest\n",
|
|
" \n",
|
|
" filter_tensor = torch.zeros(NUMBER_CONSTELLATIONS)\n",
|
|
" filter_tensor[constellation] = 1.0\n",
|
|
" \n",
|
|
" return self.partials @ filter_tensor\n",
|
|
" \n",
|
|
" def partial_matrix(constellation):\n",
|
|
" #returns the partials of the value function corresponding to the constellation of interest\n",
|
|
" \n",
|
|
" filter_tensor = torch.zeros(NUMBER_CONSTELLATIONS)\n",
|
|
" filter_tensor[constellation] = 1.0\n",
|
|
" \n",
|
|
" return self.partials * filter_tensor\n",
|
|
" \n",
|
|
" def __str__(self):\n",
|
|
" return \"Launch Decisions and Partial Derivativs of value function with\\n\\t states\\n\\t\\t {}\\n\\tPartials\\n\\t\\t{}\".format(self.states,self.partials)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "stuffed-firmware",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Setup Functions\n",
|
|
"## General CompositionFunctions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 2,
|
|
"id": "mexican-serial",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"### Set up functions to compose functions \n",
|
|
"# These functions will \n",
|
|
"# - compose two functions together\n",
|
|
"# - compose a function to itself n times.\n",
|
|
"\n",
|
|
"def compose(f,g):\n",
|
|
" \"\"\"\n",
|
|
" this function composes two functions f and g in\n",
|
|
" the order f(g(*args))\n",
|
|
" \n",
|
|
" it returns a function\n",
|
|
" \"\"\"\n",
|
|
" return lambda *args: f(g(*args))\n",
|
|
"\n",
|
|
"def compose_recursive_functions(fn,n):\n",
|
|
" \"\"\"\n",
|
|
" This function takes a function fn and composes it with itself n times\n",
|
|
" \n",
|
|
" Returns an array/list that contains each of the n composition levels, ordered\n",
|
|
" from fn() to fn^n()\n",
|
|
" \"\"\"\n",
|
|
"\n",
|
|
" \n",
|
|
" #Set base conditions\n",
|
|
" out_func = None\n",
|
|
" out_func_list =[]\n",
|
|
"\n",
|
|
" #build the compositions of functions\n",
|
|
" for f in itertools.repeat(fn, n):\n",
|
|
" #if first iteration\n",
|
|
" if out_func == None:\n",
|
|
" out_func = f\n",
|
|
" else:\n",
|
|
" out_func = compose(f,out_func)\n",
|
|
"\n",
|
|
" #append the ou\n",
|
|
" out_func_list.append(out_func)\n",
|
|
" \n",
|
|
" return out_func_list"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "public-alloy",
|
|
"metadata": {},
|
|
"source": [
|
|
"# functions related to transitions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": 3,
|
|
"id": "advised-enemy",
|
|
"metadata": {},
|
|
"outputs": [
|
|
{
|
|
"ename": "SyntaxError",
|
|
"evalue": "invalid syntax (<ipython-input-3-2a8ca63b5912>, line 52)",
|
|
"output_type": "error",
|
|
"traceback": [
|
|
"\u001b[0;36m File \u001b[0;32m\"<ipython-input-3-2a8ca63b5912>\"\u001b[0;36m, line \u001b[0;32m52\u001b[0m\n\u001b[0;31m launch = neural_network.forward().\u001b[0m\n\u001b[0m ^\u001b[0m\n\u001b[0;31mSyntaxError\u001b[0m\u001b[0;31m:\u001b[0m invalid syntax\n"
|
|
]
|
|
}
|
|
],
|
|
"source": [
|
|
"def single_transition(laws_motion_fn, profit_fn, stocks, debris, neural_network):\n",
|
|
" \"\"\"\n",
|
|
" This function represents the inverted envelope conditions.\n",
|
|
" It allows us to describe the derivatives of the value function evaluated at time $t+1$ in terms based in time period $t$.\n",
|
|
" \n",
|
|
" It takes a few derivatives (jacobians), and due to the nature of the return as tuples\n",
|
|
" they must be concatenated.\n",
|
|
" \n",
|
|
" It returns the transitioned values\n",
|
|
" \"\"\"\n",
|
|
" \n",
|
|
" launch_and_partials = neural_network.forward(stocks,debris)\n",
|
|
" \n",
|
|
" #Get the jacobian\n",
|
|
" a = jacobian(laws_motion_fn, (stocks, debris, launch_and_partials.launches))\n",
|
|
" \n",
|
|
" #Reassemble the Jacobian nested tuples into the appropriate tensor\n",
|
|
" A = BETA * torch.cat((torch.cat((a[0][0],a[0][1]),dim=1),torch.cat((a[1][0],a[1][1]),dim=1)), dim=0)\n",
|
|
"\n",
|
|
" #TODO: figure out some diagnostics for this section\n",
|
|
" #Possibilities include:\n",
|
|
" # - Det(A) ~= 0\n",
|
|
" # - EigVal(A) ~= 0\n",
|
|
" # - A.inverse() with a try catch system to record types of returns\n",
|
|
" #Alternatively, \n",
|
|
" #if abs(a.det())\n",
|
|
" \n",
|
|
" #Calculate the item to transition\n",
|
|
" f_jacobians = jacobian(profit_fn,(stocks, debris, launch_and_partials.launches))\n",
|
|
"\n",
|
|
" #issue with shape here: my launch function is for all launches, not just a single launch.\n",
|
|
" f_theta = torch.cat([f_jacobians[0][0], f_jacobians[1][0]],axis=0) \n",
|
|
"\n",
|
|
" #FIX: issue with scaling here, in that I need to get the constellation level partials, not the full system.\n",
|
|
" #I'm not sure how to get each \n",
|
|
" T = launch_and_partials.partials - f_theta\n",
|
|
"\n",
|
|
" #Includes rearranging the jacobian of profit.\n",
|
|
"\n",
|
|
" #Return the transitioned values\n",
|
|
" return ( A.inverse() ) @ T\n",
|
|
"\n",
|
|
"\n",
|
|
"# This function wraps the single transition and handles updating dates etc.\n",
|
|
"def transition_wrapper(data_in):\n",
|
|
" \"\"\"\n",
|
|
" \"\"\"\n",
|
|
" #unpack states and functions\n",
|
|
" stocks, debris,profit_fn, laws_motion_fn, neural_network = data_in\n",
|
|
" \n",
|
|
" #Calculate new states\n",
|
|
" launch = neural_network.forward().\n",
|
|
" new_stocks, new_debris = laws_motion_fn(stocks,debris, launch)\n",
|
|
"\n",
|
|
" #This gets the transition of the value function derivatives over time.\n",
|
|
" transitioned = single_transition(\n",
|
|
" laws_motion_fn, \n",
|
|
" profit_fn, \n",
|
|
" stocks, debris, #states\n",
|
|
" neural_network #launch function\n",
|
|
" )\n",
|
|
" \n",
|
|
" #collects the data back together for return, including the updated state variables\n",
|
|
" data_out = new_stocks, new_debris, profit_fn, laws_motion_fn, transitioned, launch_fn\n",
|
|
" \n",
|
|
" return data_out"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "suspected-clerk",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Setup functions related to the problem"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "confused-conclusion",
|
|
"metadata": {
|
|
"tags": []
|
|
},
|
|
"outputs": [],
|
|
"source": [
|
|
" \n",
|
|
"\n",
|
|
"### functions\n",
|
|
"\n",
|
|
"def survival(stock, debris):\n",
|
|
" \"\"\"\n",
|
|
" This is a basic, deterministic survival function. \n",
|
|
" It is based on the CDF of an exponential distribution.\n",
|
|
" \"\"\"\n",
|
|
" #SURVIVAL FUNCTION BASED ON AN EXPONENTIAL DISTRIBUTION\n",
|
|
" return 1 - torch.exp(-SCALING * stock - debris)\n",
|
|
"\n",
|
|
"\n",
|
|
"def laws_of_motion(stock, debris, neural_network):\n",
|
|
" \"\"\"\n",
|
|
" This function updates state variables (stock and debris), according \n",
|
|
" to the laws of motion.\n",
|
|
" \n",
|
|
" It returns the state variables as \n",
|
|
" \"\"\"\n",
|
|
" launches_and_partials = neural_network.forward(stock,debris)\n",
|
|
" \n",
|
|
" s = survival(stock,debris)\n",
|
|
" #Notes: Survival is a global function.\n",
|
|
" \n",
|
|
" new_stock = stock*s + launches_and_partials.launches\n",
|
|
" \n",
|
|
"\n",
|
|
" new_debris = (1-DELTA)*debris + LAUNCH_DEBRIS_RATE * launches_and_partials.launches.sum() + COLLISION_DEBRIS_RATE*(1-s) @ stock\n",
|
|
" \n",
|
|
" return (new_stock, new_debris)\n",
|
|
"\n",
|
|
"#This is not a good specification of the profit function, but it will work for now.\n",
|
|
"def profit(stock, debris, launches):\n",
|
|
" return UTIL_WEIGHTS @ stock - LAUNCH_COST*launches\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "miniature-thread",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"# Launch functions\n",
|
|
"class ModelMockup():\n",
|
|
" def __init__(self):\n",
|
|
" pass\n",
|
|
" #just a wrapper for NN like results\n",
|
|
" \n",
|
|
" def forward(self,stock, debris):\n",
|
|
" \"\"\"\n",
|
|
" Temporary launch function \n",
|
|
" \"\"\"\n",
|
|
" return LDAPV(torch.ones(len(stocks), requires_grad=True),torch.ones((len(stocks)+len(debris),len(stocks)+len(debris)), requires_grad=True))\n",
|
|
"\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "yellow-frank",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Actual calculations"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "enormous-provider",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"#number of states\n",
|
|
"N = 5\n",
|
|
"\n",
|
|
"#set states\n",
|
|
"stocks = torch.ones(N)\n",
|
|
"#Last one is different if there are more than 1 (just for variety, no theoretical reason)\n",
|
|
"if N > 1:\n",
|
|
" stocks[-1] = 0.5\n",
|
|
"#now add the tracking requirement in place\n",
|
|
"stocks.requires_grad_()\n",
|
|
"\n",
|
|
"#Setup Debris\n",
|
|
"debris = torch.tensor([2.2],requires_grad=True)\n",
|
|
"\n",
|
|
"#CHANGE LATER: Launch is currently a value, should be a function (i.e. neural network)\n",
|
|
"launches = ModelMockup()\n",
|
|
"\n",
|
|
"#Starting point\n",
|
|
"# Stocks, debris, profit fn, laws of motion, item to transition, Launch function\n",
|
|
"base_data = (stocks,debris, profit, laws_of_motion, launches)\n",
|
|
"\n",
|
|
"#Parameters\n",
|
|
"SCALING = torch.ones(5)\n",
|
|
"DELTA = 0.9\n",
|
|
"LAUNCH_DEBRIS_RATE = 0.005\n",
|
|
"LAUNCH_COST = 1.0\n",
|
|
"COLLISION_DEBRIS_RATE = 0.0007\n",
|
|
"UTIL_WEIGHTS = torch.tensor([1,-0.2,0,0,0])\n",
|
|
"BETA = 0.95\n",
|
|
"\n",
|
|
"#Constants determining iterations etc.\n",
|
|
"NUMBER_CONSTELLATIONS = 5\n",
|
|
"NUMBER_DEBRIS_TRACKERS = 1\n",
|
|
"NUMBER_OF_CHOICE_VARIABLES = 1\n",
|
|
"NUMBER_OF_REQUIRED_ITERATED_CONDITIONS = (NUMBER_CONSTELLATIONS+NUMBER_DEBRIS_TRACKERS+(NUMBER_OF_CHOICE_VARIABLES*NUMBER_CONSTELLATIONS))\n",
|
|
"NUMBER_OF_REQUIRED_ITERATIONS = math.ceil(NUMBER_OF_REQUIRED_ITERATED_CONDITIONS/NUMBER_CONSTELLATIONS)\n",
|
|
"NUMBER_OF_REQUIRED_ITERATIONS,NUMBER_OF_REQUIRED_ITERATED_CONDITIONS"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "biblical-blake",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"#Calculate the number of iterations\n",
|
|
"#Get the values from transitions\n",
|
|
"wt1 = compose_recursive_functions(transition_wrapper,NUMBER_OF_REQUIRED_ITERATIONS)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "given-clearance",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"m = ModelMockup()\n",
|
|
"print(m.forward(stocks,debris))"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "higher-windsor",
|
|
"metadata": {},
|
|
"source": [
|
|
"# Optimatility conditions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "breeding-sussex",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"#Optimality math\n",
|
|
"def optimality(stocks\n",
|
|
" ,debris\n",
|
|
" ,profit_fn\n",
|
|
" ,laws_motion_fn\n",
|
|
" ,neural_net\n",
|
|
" ):\n",
|
|
" \"\"\"\n",
|
|
" This function takes in the \n",
|
|
" - stock levels\n",
|
|
" - debris levels\n",
|
|
" - profit function\n",
|
|
" - laws of motion\n",
|
|
" - results from the neural network\n",
|
|
" and returns the parts used to make up the optimality conditions\n",
|
|
" \"\"\"\n",
|
|
" \n",
|
|
" #split out the results from the forwarded network\n",
|
|
" launch_and_partials = neural_net.forward(stock,debris)\n",
|
|
" \n",
|
|
" #Derivative of the value function with respect to choice functions\n",
|
|
" #this returns derivatives with respect to every launch, so I've removed that\n",
|
|
" fx = jacobian(profit_fn, (stocks,debris,launch_and_partials.launch))[-1][:,0]\n",
|
|
" \n",
|
|
" \n",
|
|
" #The following returns a tuple of tuples of tensors.\n",
|
|
" #the first tuple contains jacobians related to laws of motion for stocks\n",
|
|
" #the second tuple contains jacobians related to laws of motion for debris.\n",
|
|
" #we need the derivatives related to both\n",
|
|
" b = jacobian(laws_of_motion,(stocks,debris,launch_and_partials.launch))\n",
|
|
" B = torch.cat((b[0][2],b[1][2].T),axis=1)\n",
|
|
"\n",
|
|
" #return the optimality values\n",
|
|
" return fx + BETA *B @ launch_and_partials.partials\n",
|
|
"\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "actual-polish",
|
|
"metadata": {},
|
|
"source": [
|
|
"## Now to set up the recursive set of optimatliy conditions"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "thrown-subject",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def recursive_optimality(base_data,transition_wrapper):\n",
|
|
" #create and return a set of transition wrappers\n",
|
|
" for f in compose_recursive_functions(transition_wrapper,3):\n",
|
|
" result = f(base_data)\n",
|
|
"\n",
|
|
" #unpack results\n",
|
|
" new_stocks, new_debris, profit_fn, laws_motion_fn, launch_and_partials = result\n",
|
|
"\n",
|
|
" optimal = optimality(new_stocks\n",
|
|
" ,new_debris\n",
|
|
" ,profit_fn\n",
|
|
" ,laws_motion_fn\n",
|
|
" ,launch_and_partials\n",
|
|
" )\n",
|
|
" yield optimal"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "strange-appliance",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"def optimality_wrapper(stocks,debris, launches):\n",
|
|
" return optimality(stocks,debris, profit, laws_of_motion, launches)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "friendly-acrobat",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": [
|
|
"optimality_wrapper(stocks,debris,launches)"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "patient-builder",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
},
|
|
{
|
|
"cell_type": "markdown",
|
|
"id": "referenced-defense",
|
|
"metadata": {},
|
|
"source": [
|
|
"Notes so far\n",
|
|
" - This takes $\\frac{\\partial W_t}{\\partial{\\theta_t}$ as given. I need to reframe this to clean that out. \n",
|
|
" - a failed method was just to calculate the partials of W for t = t. That doesn't work.\n",
|
|
" - Becasue the B value is not invertible, you can't just solve for the partials.\n",
|
|
" - note the approach below.\n",
|
|
" - Use part of the launch NN to approximate the gradient conditions.\n",
|
|
" - Complicates things slightly as it would require adding more outputs to the NN\n",
|
|
" - Would allow for PDE solution for value function.\n",
|
|
" - This might be useful for free-entry conditions.\n",
|
|
" - Not very helpful otherwise.\n",
|
|
" - I don't think it adds much to the final analysis.\n",
|
|
" \n",
|
|
" \n",
|
|
" - There is an issue in how to handle the multiple value functions/loss functions\n",
|
|
" - Current status\n",
|
|
" - The launch function (soon to be NN) returns launch choices for each constellation.\n",
|
|
"\n"
|
|
]
|
|
},
|
|
{
|
|
"cell_type": "code",
|
|
"execution_count": null,
|
|
"id": "incorrect-carol",
|
|
"metadata": {},
|
|
"outputs": [],
|
|
"source": []
|
|
}
|
|
],
|
|
"metadata": {
|
|
"kernelspec": {
|
|
"display_name": "Python 3",
|
|
"language": "python",
|
|
"name": "python3"
|
|
},
|
|
"language_info": {
|
|
"codemirror_mode": {
|
|
"name": "ipython",
|
|
"version": 3
|
|
},
|
|
"file_extension": ".py",
|
|
"mimetype": "text/x-python",
|
|
"name": "python",
|
|
"nbconvert_exporter": "python",
|
|
"pygments_lexer": "ipython3",
|
|
"version": "3.8.8"
|
|
}
|
|
},
|
|
"nbformat": 4,
|
|
"nbformat_minor": 5
|
|
}
|