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.
Orbits/Code/ImplementLoss.ipynb

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
}