Open ChristopherRabotin opened 1 year ago
GPT-4's proposed solution:
To create an AST (Abstract Syntax Tree) parser for GMAT scripts and convert them to Python scripts that use Nyx, you'll need to follow these general steps:
Tokenize the GMAT script: Split the script into tokens (words, symbols, etc.) that can be processed individually.
Build the AST: Process the tokens to create an abstract syntax tree that represents the structure of the GMAT script.
Traverse the AST and generate the corresponding Nyx Python script.
Here's a simple example using Python's ply library (Python Lex-Yacc) to get you started:
First, install the ply library:
pip install ply
Next, create a file named gmat_parser.py with the following content:
import ply.lex as lex
import ply.yacc as yacc
# Token list
tokens = (
'CREATE', 'SPACECRAFT', 'IDENTIFIER',
'BEGINMISSIONSEQUENCE', 'ENDMISSIONSEQUENCE',
'PROPAGATE', 'LPAREN', 'RPAREN', 'LBRACE', 'RBRACE',
'SEMICOLON', 'EQUALS', 'PERIOD',
)
# Token definitions
t_CREATE = r'Create'
t_SPACECRAFT = r'Spacecraft'
t_IDENTIFIER = r'[a-zA-Z_][a-zA-Z0-9_]*'
t_BEGINMISSIONSEQUENCE = r'BeginMissionSequence'
t_ENDMISSIONSEQUENCE = r'EndMissionSequence'
t_PROPAGATE = r'Propagate'
t_LPAREN = r'\('
t_RPAREN = r'\)'
t_LBRACE = r'\{'
t_RBRACE = r'\}'
t_SEMICOLON = r';'
t_EQUALS = r'='
t_PERIOD = r'\.'
# Ignored characters
t_ignore = " \t"
# Lexer error handling
def t_error(t):
print(f"Illegal character '{t.value[0]}'")
t.lexer.skip(1)
# Build the lexer
lexer = lex.lex()
# Parsing rules
def p_script(p):
'''script : objects mission_sequence'''
p[0] = {'objects': p[1], 'mission_sequence': p[2]}
def p_objects(p):
'''objects : create_object objects
| empty'''
if len(p) == 3:
p[0] = [p[1]] + p[2]
else:
p[0] = []
def p_create_object(p):
'''create_object : CREATE SPACECRAFT IDENTIFIER SEMICOLON'''
p[0] = {'type': 'spacecraft', 'name': p[3]}
def p_mission_sequence(p):
'''mission_sequence : BEGINMISSIONSEQUENCE statements ENDMISSIONSEQUENCE'''
p[0] = p[2]
def p_statements(p):
'''statements : statement SEMICOLON statements
| empty'''
if len(p) == 4:
p[0] = [p[1]] + p[3]
else:
p[0] = []
def p_statement(p):
'''statement : propagate_statement'''
p[0] = p[1]
def p_propagate_statement(p):
'''propagate_statement : PROPAGATE IDENTIFIER LPAREN IDENTIFIER RPAREN LBRACE IDENTIFIER PERIOD IDENTIFIER EQUALS NUMBER RBRACE'''
p[0] = {
'type': 'propagate',
'propagator': p[2],
'spacecraft': p[4],
'parameter': p[7] + '.' + p[9],
'value': p[11],
}
def p_NUMBER(p):
'''NUMBER : FLOAT
| INT'''
p[0] = p[1]
def p_FLOAT(p):
r'FLOAT : \d+\.\d+'
p[0] = float(p[1])
def p_INT(p):
r'INT : \d+'
p[0] = int(p[1])
def p_empty(p):
'empty :'
pass
# Error rule for syntax errors
def p_error(p):
print(f"Syntax error in input: {p}")
# Build the parser
parser = yacc.yacc()
# Test the parser
if __name__ == "__main__":
test_gmat_script = """
Create Spacecraft MySpacecraft;
BeginMissionSequence
Propagate MyPropagator(MySpacecraft) {MySpacecraft.ElapsedDays = 1};
EndMissionSequence
"""
ast = parser.parse(test_gmat_script)
print(ast)
This example demonstrates a basic GMAT script parser using Python's ply library. It defines the lexer and parser rules for a limited set of GMAT script instructions, specifically focusing on the Create Spacecraft and Propagate instructions.
To create the corresponding Nyx Python script, you'll need to traverse the generated AST and write functions that output the relevant Nyx Python code for each GMAT instruction.
For instance, you could create a function gmat_ast_to_nyx(ast: dict) -> str that takes the AST generated by the parser and returns a string containing the corresponding Nyx Python script.
Keep in mind that this is a simple example and only covers a limited set of GMAT instructions. You'll need to expand the lexer and parser rules to handle a broader range of GMAT instructions to create a more comprehensive AST parser.
GPT-4 when asked to use Lark instead (seems to be recommended).
Sure! Here's the same GMAT script parser implemented using Lark. First, install the lark-parser library:
pip install lark-parser
Next, create a file named gmat_parser_lark.py with the following content:
from lark import Lark, Transformer
grammar = """
start: objects mission_sequence
objects: create_object*
create_object: "Create" "Spacecraft" IDENTIFIER ";"
mission_sequence: "BeginMissionSequence" statement* "EndMissionSequence"
statement: propagate_statement ";"
propagate_statement: "Propagate" IDENTIFIER "(" IDENTIFIER ")" "{" IDENTIFIER "." IDENTIFIER "=" NUMBER "}"
NUMBER: FLOAT | INT
FLOAT: /\d+\.\d+/
INT: /\d+/
IDENTIFIER: /[a-zA-Z_][a-zA-Z0-9_]*/
%import common.WS
%ignore WS
"""
class GmatTransformer(Transformer):
def start(self, items):
return {'objects': items[0], 'mission_sequence': items[1]}
def objects(self, items):
return items
def create_object(self, items):
return {'type': 'spacecraft', 'name': items[0]}
def mission_sequence(self, items):
return items
def statement(self, items):
return items[0]
def propagate_statement(self, items):
return {
'type': 'propagate',
'propagator': items[0],
'spacecraft': items[1],
'parameter': items[2] + '.' + items[3],
'value': items[4],
}
def NUMBER(self, token):
try:
return int(token)
except ValueError:
return float(token)
def IDENTIFIER(self, token):
return token[0]
parser = Lark(grammar, parser="lalr", transformer=GmatTransformer())
if __name__ == "__main__":
test_gmat_script = """
Create Spacecraft MySpacecraft;
BeginMissionSequence
Propagate MyPropagator(MySpacecraft) {MySpacecraft.ElapsedDays = 1};
EndMissionSequence
"""
ast = parser.parse(test_gmat_script)
print(ast)
This implementation defines the grammar using Lark's EBNF-like syntax and uses a Transformer class to transform the parsed tree into an AST. The syntax for defining the grammar is more concise and expressive, and the generated AST will be similar to the one produced by the PLY-based implementation.
As with the PLY example, you will need to traverse the generated AST and write functions that output the relevant Nyx Python code for each GMAT instruction to convert the GMAT script to a Nyx Python script.
(Later)
Here's a function named gmat_ast_to_nyx() that takes the Lark AST generated by the parser and returns a string containing the corresponding Nyx Python script. This function assumes that the GMAT script contains only the Create Spacecraft and Propagate instructions, as shown in the previous examples. You will need to expand this function to handle other GMAT instructions as needed.
def gmat_ast_to_nyx(ast: dict) -> str:
# Initialize an empty list to store lines of the Nyx script
nyx_lines = []
# Handle objects (Spacecraft)
for obj in ast['objects']:
if obj['type'] == 'spacecraft':
nyx_lines.append(f"# Create Spacecraft {obj['name']}")
nyx_lines.append(f"sc = Spacecraft() # Replace with proper Spacecraft initialization")
# Handle mission sequence
for stmt in ast['mission_sequence']:
if stmt['type'] == 'propagate':
nyx_lines.append(f"# Propagate {stmt['propagator']}({stmt['spacecraft']}) {{ {stmt['parameter']} = {stmt['value']} }}")
nyx_lines.append(f"dynamics = SpacecraftDynamics() # Replace with proper SpacecraftDynamics initialization")
nyx_lines.append(f"duration = Duration(Unit.Day * {stmt['value']})")
nyx_lines.append(f"rslt, traj = propagate(sc, dynamics, duration)")
# Return the Nyx script as a single string
return '\n'.join(nyx_lines)
You can use this function in conjunction with the Lark parser to convert a GMAT script into a Nyx Python script. Here's an example of how to do that:
# Parse the GMAT script using the Lark parser
ast = parser.parse(test_gmat_script)
# Convert the GMAT AST to a Nyx Python script
nyx_script = gmat_ast_to_nyx(ast)
# Print the Nyx script
print(nyx_script)
Please note that this function only provides a starting point for the conversion and includes placeholders for initializing the Spacecraft and SpacecraftDynamics objects. You will need to replace these placeholders with the appropriate initialization code based on the information available in your GMAT script or external configuration files.
As you expand your GMAT parser to handle more instructions, you will also need to update the gmat_ast_to_nyx() function to generate the corresponding Nyx Python code for each new instruction.
GPT-4
I will show you how to parse the GMAT ForceModel and Propagator definitions using Lark and convert them into the corresponding Nyx YAML format.
First, let's create a Lark grammar to parse the given GMAT script:
gmat_force_model_grammar = r"""
start: force_model* propagator*
force_model: "Create ForceModel" NAME force_model_item+
force_model_item: NAME "." NAME "=" (NAME | "{" NAME ("," NAME)* "}" | "On" | "None" | NUMBER)
propagator: "Create Propagator" NAME propagator_item+
propagator_item: NAME "." NAME "=" (NAME | NUMBER)
%import common.CNAME -> NAME
%import common.NUMBER
%import common.WS
%ignore WS
"""
parser = Lark(gmat_force_model_grammar, start="start", parser="lalr")
Now we can parse the GMAT script and extract the ForceModel and Propagator information:
gmat_script = """
%----------------------------------------------------
% Configure propagators and force models
%----------------------------------------------------
... % Your GMAT script content here
"""
ast = parser.parse(gmat_script)
Next, let's create a function named gmat_force_models_and_propagators_to_nyx_yaml() that takes the parsed Lark AST and converts it into the corresponding Nyx YAML format:
def gmat_force_models_and_propagators_to_nyx_yaml(ast: lark.Tree) -> str:
force_models = {}
for item in ast.children:
if item.data == "force_model":
force_model = {}
name = item.children[0]
for attr in item.children[1:]:
key = attr.children[0] + "." + attr.children[1]
value = attr.children[2]
force_model[key] = value
force_models[name] = force_model
# Convert GMAT force models to Nyx format
nyx_force_models = {}
for name, model in force_models.items():
nyx_model = {}
point_masses = []
for key, value in model.items():
if key.endswith("PointMasses"):
point_masses.extend(value.children)
if point_masses:
nyx_model['point_masses'] = point_masses
if "GravityField" in name:
nyx_model['harmonics'] = [{
'frame': model.get('GravityField.Earth.Frame', 'IAU Earth'),
'coeffs': model.get('GravityField.Earth.Coeffs', 'data/JGM3.cof.gz'),
'degree': model.get('GravityField.Earth.Degree', 10),
'order': model.get('GravityField.Earth.Order', 10),
}]
if "SRP" in name:
nyx_model['srp'] = {
'phi': 1367.0, # Default value, you can customize it as needed
'shadows': point_masses,
}
nyx_force_models[name] = nyx_model
return yaml.dump(nyx_force_models)
You can use this function to convert the parsed Lark AST of the GMAT script to a Nyx YAML string:
Co-author: Claude.
High level description
We will implement a script to automatically convert NASA GMAT .script files into equivalent Nyx Python scripts. By parsing the GMAT script into an abstract syntax tree, we can map each element to its Nyx Python API equivalent to generate a full Nyx script.
Requirements
Write a GMAT script parser to construct an abstract syntax tree (AST) from a .script file. The parser should:
Map each node in the GMAT AST to its equivalent Nyx Python API call. For example:
Propagate
-> Nyx PropagatorForceModel
-> Nyx Orbital and Spacecraft dynamicsGenerate formatted Python code from the mapped AST (e.g. run
ruff
).Handle GMAT syntax/functions that do not have a direct Nyx equivalent. In some cases, manual intervention or editing of the output script may be required. Provide clear warnings where full automation was not possible. The idea here would be to store parallel errors and only crash once, showing all of the errors that failed.
Provide a CLI to run the conversion script, passing a GMAT .script file as input and printing the Nyx Python code to a folder that contains the recommended skeleton for a Nyx project.
Add tests to validate the correct conversion of various GMAT script examples and catch edge cases. Test for accurate Python syntax, Nyx API usage, and runtime correctness of the output scripts.
Update documentation to describe the GMAT script conversion feature, include installation/usage of the CLI, and link to examples.
Test plans
Here are the test plans for the GMAT script conversion feature:
Test Plans
Unit tests
End-to-end examples
Edge cases
CLI usage
Documentation
Limitations
Future work
Design
Abstract syntax tree are great. That's it really.