Skip to content

Commit

Permalink
Created _sort_components_and_datatypes function to perform topologica…
Browse files Browse the repository at this point in the history
…l sort
  • Loading branch information
Ananya2003Gupta committed Nov 14, 2023
1 parent 9fa8a68 commit 96bdc65
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 78 deletions.
1 change: 1 addition & 0 deletions doc/templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,7 @@ The available fields are
| `full_type` | The fully qualified type, corresponding to `{{ namespace }}::{{ bare_type }}`. |

### Julia code generation
It is an experimental feature.
Builtin types mapping in Julia
| cpp | julia |
|-------------|--------------------------------------------------------------------------------|
Expand Down
103 changes: 67 additions & 36 deletions python/podio_class_generator.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,11 @@
Read instructions in the README.md to run your first example!
"""
REPORT_TEXT_JULIA = """
Julia Code generation is an experimental feature.
Warning: ExtraCode and MutableExtraCode will be ignored during julia code generation.
PODIO Data Model
================
Used {yamlfile} to create {nclasses} julia files in {installdir}/
Used {yamlfile} to create {nfiles} julia files in {installdir}/
Read instructions in the README.md to run your first example!
"""

Expand Down Expand Up @@ -154,6 +155,9 @@ def process(self):
for name, datatype in self.datamodel.datatypes.items():
datamodel['datatypes'].append(self._process_datatype(name, datatype))

datamodel['static_arrays_import'] = self._has_static_arrays_import(datamodel['components'] + datamodel['datatypes'])
datamodel['includes'] = self._sort_components_and_datatypes(datamodel['components'] + datamodel['datatypes'])

if self.proglang == "julia":
self._process_parent_module(datamodel)

Expand Down Expand Up @@ -205,9 +209,9 @@ def print_report(self):
if not self.verbose:
return
if self.proglang == "julia":
nclasses = len(self.datamodel.datatypes) + len(self.datamodel.components) + 1
nfiles = len(self.datamodel.datatypes) + len(self.datamodel.components) + 1
text = REPORT_TEXT_JULIA.format(yamlfile=self.yamlfile,
nclasses=nclasses,
nfiles=nfiles,
installdir=self.install_dir)
if self.proglang == "cpp":
nclasses = 5 * len(self.datamodel.datatypes) + len(self.datamodel.components)
Expand Down Expand Up @@ -296,18 +300,15 @@ def _process_component(self, name, component):
# original definition can be left untouched
# pylint: disable=too-many-nested-blocks
component = deepcopy(component)
includes, includes_jl = set(), set()
includes = set()
includes.update(*(m.includes for m in component['Members']))
includes_jl.update(*(m.jl_imports for m in component['Members']))
for member in component['Members']:
if not (member.is_builtin or member.is_builtin_array):
includes.add(self._build_include(member))
includes_jl.add(self._build_julia_include(member))

includes.update(component.get("ExtraCode", {}).get("includes", "").split('\n'))

component['includes'] = self._sort_includes(includes)
component['includes_jl'] = {'struct': sorted(includes_jl)}
component['class'] = DataType(name)
component['upstream_edm'] = self.upstream_edm
component['upstream_edm_name'] = ''
Expand Down Expand Up @@ -422,15 +423,6 @@ def prepare_iorules(self):
else:
raise NotImplementedError(f"Schema evolution for {schema_change} not yet implemented.")

def _preprocess_for_julia(self, datatype):
"""Do the preprocessing that is necessary for Julia code generation"""
includes_jl_struct = set()
for member in datatype['VectorMembers']:
if not member.is_builtin:
includes_jl_struct.add(self._build_julia_include(member))
datatype['includes_jl']['struct'].update((includes_jl_struct))
datatype['includes_jl']['struct'] = sorted(datatype['includes_jl']['struct'])

@staticmethod
def _get_julia_params(datatype):
"""Get the relations as parameteric types for MutableStructs"""
Expand Down Expand Up @@ -571,7 +563,6 @@ def _preprocess_datatype(self, name, definition):
data = deepcopy(definition)
data['class'] = DataType(name)
data['includes_data'] = self._get_member_includes(definition["Members"])
data['includes_jl'] = {'struct': self._get_member_includes(definition["Members"], julia=True)}
data['params_jl'] = sorted(self._get_julia_params(data), key=lambda x: x[0])
data['upstream_edm'] = self.upstream_edm
data['upstream_edm_name'] = ''
Expand All @@ -580,8 +571,6 @@ def _preprocess_datatype(self, name, definition):
self._preprocess_for_class(data)
self._preprocess_for_obj(data)
self._preprocess_for_collection(data)
self._preprocess_for_julia(data)

return data

def _write_edm_def_file(self):
Expand All @@ -603,25 +592,20 @@ def quoted_sv(string):
self._write_file('DatamodelDefinition.h',
self._eval_template('DatamodelDefinition.h.jinja2', data))

def _get_member_includes(self, members, julia=False):
def _get_member_includes(self, members):
"""Process all members and gather the necessary includes"""
includes, includes_jl = set(), set()
includes = set()
includes.update(*(m.includes for m in members))
includes_jl.update(*(m.jl_imports for m in members))
for member in members:
if member.is_array and not member.is_builtin_array:
include_from = IncludeFrom.INTERNAL
if self.upstream_edm and member.array_type in self.upstream_edm.components:
include_from = IncludeFrom.EXTERNAL
includes.add(self._build_include_for_class(member.array_bare_type, include_from))
includes_jl.add(self._build_julia_include_for_class(member.array_bare_type, include_from))

includes.add(self._build_include(member))
includes_jl.add(self._build_julia_include(member))

if not julia:
return self._sort_includes(includes)
return includes_jl
return self._sort_includes(includes)

def _write_cmake_lists_file(self):
"""Write the names of all generated header and src files into cmake lists"""
Expand Down Expand Up @@ -695,17 +679,64 @@ def _build_include_for_class(self, classname, include_from: IncludeFrom) -> str:
# the generated code)
return ''

def _build_julia_include(self, member) -> str:
"""Return the include statement for julia"""
return self._build_julia_include_for_class(member.bare_type, self._needs_include(member.full_type))
@staticmethod
def _sort_components_and_datatypes(data):
"""Sorts a list of components and datatypes based on dependencies, ensuring that components and datatypes
with no dependencies or dependencies on built-in types come first. The function performs
topological sorting using Kahn's algorithm."""
# Create a dictionary to store dependencies
dependencies = {}
bare_types_mapping = {}

for component_data in data:
full_type = component_data['class'].full_type
bare_type = component_data['class'].bare_type
bare_types_mapping[full_type] = bare_type
dependencies[full_type] = set()

# Check dependencies in 'Members'
if 'Members' in component_data:
for member_data in component_data['Members']:
member_full_type = member_data.full_type
if not member_data.is_builtin and not member_data.is_builtin_array:
dependencies[full_type].add(member_full_type)

# Check dependencies in 'VectorMembers'
if 'VectorMembers' in component_data:
for vector_member_data in component_data['VectorMembers']:
vector_member_full_type = vector_member_data.full_type
if not vector_member_data.is_builtin and not vector_member_data.is_builtin_array:
dependencies[full_type].add(vector_member_full_type)

# Perform topological sorting using Kahn's algorithm
sorted_components = []
while dependencies:
ready = {component for component, deps in dependencies.items() if not deps}
if not ready:
sorted_components.extend(bare_types_mapping[component] for component in dependencies)
break

for component in ready:
del dependencies[component]
sorted_components.append(bare_types_mapping[component])

for deps in dependencies.values():
deps -= ready

# Return the Sorted Components (bare_types)
return sorted_components

@staticmethod
def _build_julia_include_for_class(classname, include_from: IncludeFrom) -> str:
"""Return the include statement for julia for this specific class"""
if include_from == IncludeFrom.INTERNAL:
# If we have an internal include all includes should be relative
return f'include("{classname}Struct.jl")'
return ''
def _has_static_arrays_import(data):
"""Checks if any member within a list of components and datatypes contains the import statement
'using StaticArrays' in its jl_imports. Returns True if found in any member, otherwise False."""
for component_data in data:
members_data = component_data.get('Members', [])
for member_data in members_data:
jl_imports = member_data.jl_imports
if 'using StaticArrays' in jl_imports:
return True
return False

def _sort_includes(self, includes):
"""Sort the includes in order to try to have the std includes at the bottom"""
Expand Down
3 changes: 0 additions & 3 deletions python/templates/MutableStruct.jl.jinja2
Original file line number Diff line number Diff line change
@@ -1,7 +1,4 @@
{% import "macros/params.jinja2" as params %}
{% for include in includes_jl['struct'] %}
{{ include }}
{% endfor %}
mutable struct {{ class.bare_type }}Struct{{ params.julia_parameters(params_jl,"T" ) }}
{% for member in Members %}
{% if member.is_array %}
Expand Down
44 changes: 5 additions & 39 deletions python/templates/ParentModule.jl.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ export {{ datatype['class'].bare_type }}Collection
import ..{{ upstream_edm_name }}
{% endif %}

{% for component in components %}
include("{{ component['class'].bare_type }}Struct.jl")
{% endfor %}
{% for datatype in datatypes %}
include("{{ datatype['class'].bare_type }}Struct.jl")
{% if static_arrays_import %}
using StaticArrays
{% endif %}
{% for sort_include in includes %}
include("{{ sort_include }}Struct.jl")
{% endfor %}

{% for component in components %}
Expand All @@ -47,31 +47,6 @@ function {{ component['class'].bare_type }}(
{% endif %}
{% endif %}
{% endfor %}
{% for relation in component['OneToManyRelations'] %}
{% if upstream_edm and (relation.full_type in upstream_edm.components or relation.full_type in upstream_edm.datatypes) %}
{{ relation.name }}::Vector{ {{ upstream_edm_name }}.{{ relation.julia_type }}Struct } = Vector{ {{ upstream_edm_name }}.{{ relation.julia_type }}Struct }(),
{% else %}
{{ relation.name }}::Vector{ {{ relation.julia_type }}Struct } = Vector{ {{ relation.julia_type }}Struct }(),
{% endif %}
{% endfor %}
{% for relation in component['OneToOneRelations'] %}
{% if upstream_edm and (relation.full_type in upstream_edm.components or relation.full_type in upstream_edm.datatypes) %}
{{ relation.name }}::Union{Nothing, {{ upstream_edm_name }}.{{ relation.julia_type }}Struct } = nothing,
{% else %}
{{ relation.name }}::Union{Nothing, {{ relation.julia_type }}Struct } = nothing,
{% endif %}
{% endfor %}
{% for member in component['VectorMembers'] %}
{% if member.is_builtin %}
{{ member.name }}::Vector{ {{ member.julia_type }} } = Vector{ {{ member.julia_type }} }([]),
{% else %}
{% if upstream_edm and (member.full_type in upstream_edm.components or member.full_type in upstream_edm.datatypes) %}
{{ member.name }}::Vector{ {{ upstream_edm_name }}.{{ member.julia_type }}Struct } = Vector{ {{ upstream_edm_name }}.{{ member.julia_type }}Struct }([]),
{% else %}
{{ member.name }}::Vector{ {{ member.julia_type }}Struct } = Vector{ {{ member.julia_type }}Struct }([]),
{% endif %}
{% endif %}
{% endfor %}
)
return {{ component['class'].bare_type }}Struct{{ params.julia_parameters(component['params_jl'], "Struct", upstream_edm, upstream_edm_name) }}(
{% for member in component['Members'] %}
Expand All @@ -81,15 +56,6 @@ function {{ component['class'].bare_type }}(
{{ member.name }},
{% endif %}
{% endfor %}
{% for relation in component['OneToManyRelations'] %}
{{ relation.name }},
{% endfor %}
{% for relation in component['OneToOneRelations'] %}
{{ relation.name }},
{% endfor %}
{% for member in component['VectorMembers'] %}
{{ member.name }},
{% endfor %}
)
end

Expand Down

0 comments on commit 96bdc65

Please sign in to comment.