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.
281 lines
9.7 KiB
Python
281 lines
9.7 KiB
Python
import torch
|
|
from torch.autograd.functional import jacobian
|
|
import itertools
|
|
import math
|
|
|
|
|
|
############### CONSTANTS ###################
|
|
#Parameters
|
|
BETA = 0.95
|
|
|
|
#Constants determining iterations etc.
|
|
NUMBER_CONSTELLATIONS = 5
|
|
NUMBER_DEBRIS_TRACKERS = 1
|
|
NUMBER_OF_CHOICE_VARIABLES = 1
|
|
NUMBER_OF_REQUIRED_ITERATED_CONDITIONS = (NUMBER_CONSTELLATIONS+NUMBER_DEBRIS_TRACKERS+(NUMBER_OF_CHOICE_VARIABLES*NUMBER_CONSTELLATIONS))
|
|
NUMBER_OF_REQUIRED_ITERATIONS = math.ceil(NUMBER_OF_REQUIRED_ITERATED_CONDITIONS/NUMBER_CONSTELLATIONS)
|
|
|
|
############# COMPOSITION FUNCTIONS ###################
|
|
|
|
def compose(f,g):
|
|
"""
|
|
this function composes two functions f and g in
|
|
the order f(g(*args))
|
|
|
|
it returns a function.
|
|
"""
|
|
return lambda *args: f(g(*args))
|
|
|
|
def recursively_compose_functions(fn,n):
|
|
"""
|
|
This function takes a function fn and composes it with itself n times.
|
|
|
|
Returns an array/list that contains each of the n+1 composition levels, ordered
|
|
from fn() to fn^n()
|
|
"""
|
|
#Set base conditions
|
|
out_func = None
|
|
out_func_list = []
|
|
|
|
#build the compositions of functions
|
|
for f in itertools.repeat(fn, n):
|
|
#if first iteration
|
|
if out_func == None:
|
|
out_func = f
|
|
else:
|
|
out_func = compose(f,out_func)
|
|
|
|
#append the ou
|
|
out_func_list.append(out_func)
|
|
|
|
return out_func_list
|
|
|
|
############### Physical Model ###################
|
|
class PhysicalModel():
|
|
"""
|
|
This is designed as a default, base class for the physical model of satellite interactions.
|
|
|
|
It captures the constants that characterize interactions, and provides a set of
|
|
function to calculate changes to the physical environment.
|
|
"""
|
|
def __init__(self
|
|
,collision_debris
|
|
,constellations_collision_risk
|
|
,debris_decay_rate
|
|
,launch_debris
|
|
,debris_autocatalysis_rate
|
|
)
|
|
self.collision_debris = collision_debris
|
|
self.constellations_collision_risk = constellations_collision_risk
|
|
self.debris_decay_rate = debris_decay_rate
|
|
self.launch_debris = launch_debris
|
|
self.debris_autocatalysis_rate = debris_autocatalysis_rate
|
|
|
|
|
|
def survival(self):
|
|
#returns the survival rate (not destruction rate) for the given constellation.
|
|
return 1- torch.exp(-self.constellations_collision_risk * self.stock - self.debris)
|
|
|
|
def transition_debris(self, state, launch_decisions):
|
|
"""
|
|
This function transitions debris levels based off of a state and launch decision.
|
|
"""
|
|
|
|
new_debris = (1-self.debris_decay_rate + self.debris_autocatalysis_rate) * state.debris \ #debris decay and autocatalysis
|
|
+ self.launch_debris*launch_decisions.sum() \ #debris from launches
|
|
+ self.collision_debris * (1-self.survival()) @ state.stocks
|
|
return new_debris
|
|
|
|
def transition_stocks(self, state, launch_decisions):
|
|
|
|
new_stock = self.survival() * state.stocks + launch_decisions
|
|
|
|
return new_stock
|
|
|
|
def transition(self, state, launch_decisions):
|
|
"""
|
|
This function takes a state and launch decision, and updates the state according to the physical laws of motion.
|
|
|
|
It returns a State object.
|
|
"""
|
|
d = self.transition_debris(state, launch_decisions)
|
|
s = self.transition_stocks(state, launch_decisions)
|
|
|
|
return States(s,d)
|
|
|
|
class States():
|
|
"""
|
|
This is supposed to capture the state variables of the model, to create a common interface
|
|
when passing between functions.
|
|
"""
|
|
def __init__(self, stocks,debris):
|
|
self.stocks = stocks
|
|
self.debris = debris
|
|
|
|
################ NEURAL NETWORK TOOLS ###################
|
|
|
|
class EstimandInterface():
|
|
"""
|
|
This defines a clean interface for working with the estimand (i.e. thing we are trying to estimate).
|
|
In general, we are trying to estimate the choice variables and the partial derivatives of the value functions.
|
|
This
|
|
|
|
This class wraps output for the neural network (or other estimand), allowing me to
|
|
- easily substitute various types of launch functions by having a common interface
|
|
- this eases testing
|
|
- check dimensionality etc without dealing with randomness
|
|
- again, easing testing
|
|
- reason more cleanly about the component pieces
|
|
- easing programming
|
|
- provide a clean interface to find constellation level launch decisions etc.
|
|
|
|
It takes inputs of two general categories:
|
|
- the choice function results
|
|
- the partial derivatives of the value function
|
|
"""
|
|
def __init__(self, partials, launches, deorbits=None):
|
|
self.partials = partials
|
|
self.launches = launches
|
|
self.deorbits = deorbits
|
|
|
|
def launch_single(constellation):
|
|
#returns the launch decision for the constellation of interest
|
|
|
|
filter_tensor = torch.zeros(NUMBER_CONSTELLATIONS)
|
|
filter_tensor[constellation] = 1.0
|
|
|
|
return self.launches @ filter_tensor
|
|
|
|
def launch_vector(constellation):
|
|
#returns the launch decision for the constellation of interest as a vector
|
|
|
|
filter_tensor = torch.zeros(NUMBER_CONSTELLATIONS)
|
|
filter_tensor[constellation] = 1.0
|
|
|
|
return self.launches * filter_tensor
|
|
|
|
def partial_vector(constellation):
|
|
#returns the partials of the value function corresponding to the constellation of interest
|
|
|
|
filter_tensor = torch.zeros(NUMBER_CONSTELLATIONS)
|
|
filter_tensor[constellation] = 1.0
|
|
|
|
return self.partials @ filter_tensor
|
|
|
|
def partial_matrix(constellation):
|
|
#returns the partials of the value function corresponding to
|
|
#the constellation of interest as a matrix
|
|
|
|
filter_tensor = torch.zeros(NUMBER_CONSTELLATIONS)
|
|
filter_tensor[constellation] = 1.0
|
|
|
|
return self.partials * filter_tensor
|
|
|
|
def __str__(self):
|
|
#just a human readable descriptor
|
|
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)
|
|
|
|
|
|
############## ECONOMIC MODEL ############
|
|
|
|
class EconomicModel():
|
|
"""
|
|
This class describes the set of profit functions involved in the value function iteration.
|
|
"""
|
|
def __init__(self,discount_factor, profit_objects):
|
|
self.discount_factor = discount_factor
|
|
self.profit_objects = profit_objects #A list of Profit objects
|
|
|
|
def constellation_period_profits(self, state, estimand_interface, constellation):
|
|
"""
|
|
This function calculates the current period profits for a single given constellation.
|
|
"""
|
|
return self.profit_objects[constellation].profit(state,estimand_interface,constellation)
|
|
|
|
def period_profits(self, state, estimand_interface):
|
|
"""
|
|
This function calculates the current period profits for each constellation.
|
|
"""
|
|
profits = []
|
|
for i,profit_object in self.profit_objectives:
|
|
constellation_profit = self.constellation_period_profits(state, estimand_interface, i)
|
|
profits.append(constellation_profit)
|
|
return profits
|
|
|
|
@property
|
|
def number_constellations(self):
|
|
return len(profit_objects)
|
|
|
|
|
|
#Abstract class describing profit. Each subclass will connect a profit function "style" to a specific instance of parameters.
|
|
class ProfitFunctions(metaclass=abc.ABCMetaclass):
|
|
@abc.abstractmethod
|
|
def profit(self,state,estimand_interface,constellation_number):
|
|
pass
|
|
#TODO: Should I attach the jacobian here? It may simplify things.
|
|
#def jacobian()
|
|
|
|
class LinearProfit(ProfitFunctions):
|
|
"""
|
|
The simplest type of profit function available.
|
|
"""
|
|
def __init__(self, benefit_weight, launch_cost, deorbit_cost=0):
|
|
self.benefit_weights = benefit_weight
|
|
self.launch_cost = launch_cost
|
|
self.deorbit_cost = deorbit_cost
|
|
|
|
def profit(self, state, estimand_interface, constellation_number):
|
|
#Calculate the profit
|
|
profits = self.benefit_weights @ state.stock \
|
|
- self.launch_cost * estimand_interface.launches[constellation_number] #\
|
|
#- deorbit_cost @ estimand_interface.deorbits[constellation_number]
|
|
return profits
|
|
|
|
#other profit functions to implement
|
|
# price competition (substitution)
|
|
# military (complementarity)
|
|
|
|
############### TRANSITION AND OPTIMALITY FUNCTIONS #################
|
|
#Rewrite these to use the abstractiosn present earlier
|
|
|
|
def single_transition(physical_model, economic_model, states, estimand_interface):
|
|
"""
|
|
This function represents the inverted envelope conditions.
|
|
It allows us to describe the derivatives of the value function evaluated at time $t+1$ in terms based in time period $t$.
|
|
|
|
It takes a few derivatives (jacobians), and due to the nature of the return as tuples
|
|
they must be concatenated.
|
|
|
|
It returns the transitioned values
|
|
"""
|
|
#TODO: rewrite using the current abstractions
|
|
#possibly move jacobians of profit functions and physical models to those classes
|
|
|
|
pass
|
|
|
|
def transition_wrapper(data_in): # Identify a way to eliminate this maybe?
|
|
"""
|
|
This function wraps the single transition and handles updating states etc.
|
|
|
|
"""
|
|
#TODO: rewrite using current abstractions
|
|
pass
|
|
|
|
#Optimality math
|
|
def optimality(stocks
|
|
,debris
|
|
,profit_fn
|
|
,laws_motion_fn
|
|
,neural_net
|
|
):
|
|
"""
|
|
This function takes in the
|
|
- stock levels
|
|
- debris levels
|
|
- profit function
|
|
- laws of motion
|
|
- results from the neural network
|
|
and returns the parts used to make up the optimality conditions
|
|
"""
|
|
pass
|