Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow to specify units #445

Merged
merged 10 commits into from
Aug 22, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions doc/datamodel_syntax.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,13 @@ It is also possible to specify default values for members via
Note that in this case it is extremely expensive to check whether the provided `default-value` results in valid c++.
Hence, there is only a very basic syntax check, but no actual type check, and wrong default values will be caught only when trying to compile the datamodel.

For describing physics quantities it is important to know their units. Thus it is possible to add the units to the member definition:

```yaml
Members:
<type> <name>{<default-value>} [<unit>] // <comment>
```


### Definition of references between objects:
There can be one-to-one-relations and one-to-many relations being stored in a particular class. This happens either in the `OneToOneRelations` or `OneToManyRelations` section of the data definition. The definition has again the form:
Expand Down
17 changes: 14 additions & 3 deletions python/podio/generator_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ def __init__(self, name, **kwargs):
self.full_type = kwargs.pop('type', '')
self.description = kwargs.pop('description', '')
self.default_val = kwargs.pop('default_val', None)
self.unit = kwargs.pop('unit', None)
self.is_builtin = False
self.is_builtin_array = False
self.is_array = False
Expand Down Expand Up @@ -150,6 +151,15 @@ def __init__(self, name, **kwargs):
else:
self.namespace, self.bare_type = _get_namespace_class(self.full_type)

@property
def docstring(self):
"""Docstring to be used in code generation"""
if self.unit is not None:
docstring = rf'{self.description} [{self.unit}]'
else:
docstring = self.description
return docstring

def __str__(self):
"""string representation"""
# Make sure to include scope-operator if necessary
Expand All @@ -163,8 +173,8 @@ def __str__(self):
else:
definition = rf'{scoped_type} {self.name}{{}};'

if self.description:
definition += rf' ///< {self.description}'
if self.docstring:
definition += rf' ///< {self.docstring}'
return definition

def getter_name(self, get_syntax):
Expand All @@ -190,7 +200,8 @@ def _to_json(self):
# things again here from available information
def_val = f'{{{self.default_val}}}' if self.default_val else ''
description = f' // {self.description}' if self.description else ''
return f'{self.full_type} {self.name}{def_val}{description}'
unit = f'[{self.unit}]' if self.unit else ''
return f'{self.full_type} {self.name}{def_val}{unit}{description}'


class DataModel: # pylint: disable=too-few-public-methods
Expand Down
30 changes: 17 additions & 13 deletions python/podio/podio_config_reader.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ class MemberParser:
name_str = r'([a-zA-Z_]+\w*)'
name_re = re.compile(name_str)

# Units are given in square brakets
unit_str = r'(?:\[([a-zA-Z_*\/]+\w*)\])?'
unit_re = re.compile(unit_str)

# Comments can be anything after //
# stripping of trailing whitespaces is done later as it is hard to do with regex
comment_str = r'\/\/ *(.*)'
Expand All @@ -41,12 +45,12 @@ class MemberParser:
def_val_str = r'(?:{(.+)})?'

array_re = re.compile(array_str)
full_array_re = re.compile(rf'{array_str} *{name_str} *{def_val_str} *{comment_str}')
member_re = re.compile(rf' *{type_str} +{name_str} *{def_val_str} *{comment_str}')
full_array_re = re.compile(rf'{array_str} *{name_str} *{def_val_str} *{unit_str} *{comment_str}')
member_re = re.compile(rf' *{type_str} +{name_str} *{def_val_str} *{unit_str} *{comment_str}')

# For cases where we don't require a description
bare_member_re = re.compile(rf' *{type_str} +{name_str} *{def_val_str}')
bare_array_re = re.compile(rf' *{array_str} +{name_str} *{def_val_str}')
bare_member_re = re.compile(rf' *{type_str} +{name_str} *{def_val_str} *{unit_str}')
bare_array_re = re.compile(rf' *{array_str} +{name_str} *{def_val_str} *{unit_str}')

@staticmethod
def _parse_with_regexps(string, regexps_callbacks):
Expand All @@ -56,32 +60,32 @@ def _parse_with_regexps(string, regexps_callbacks):
result = rgx.match(string)
if result:
return callback(result)

raise DefinitionError(f"'{string}' is not a valid member definition. Check syntax of the member definition.")

@staticmethod
def _full_array_conv(result):
"""MemberVariable construction for array members with a docstring"""
typ, size, name, def_val, comment = result.groups()
return MemberVariable(name=name, array_type=typ, array_size=size, description=comment.strip(), default_val=def_val)
typ, size, name, def_val, unit, comment = result.groups()
return MemberVariable(name=name, array_type=typ, array_size=size, description=comment.strip(),
unit=unit, default_val=def_val)

@staticmethod
def _full_member_conv(result):
"""MemberVariable construction for members with a docstring"""
klass, name, def_val, comment = result.groups()
return MemberVariable(name=name, type=klass, description=comment.strip(), default_val=def_val)
klass, name, def_val, unit, comment = result.groups()
return MemberVariable(name=name, type=klass, description=comment.strip(), unit=unit, default_val=def_val)

@staticmethod
def _bare_array_conv(result):
"""MemberVariable construction for array members without docstring"""
typ, size, name, def_val = result.groups()
return MemberVariable(name=name, array_type=typ, array_size=size, default_val=def_val)
typ, size, name, def_val, unit = result.groups()
return MemberVariable(name=name, array_type=typ, array_size=size, unit=unit, default_val=def_val)

@staticmethod
def _bare_member_conv(result):
"""MemberVarible construction for members without docstring"""
klass, name, def_val = result.groups()
return MemberVariable(name=name, type=klass, default_val=def_val)
klass, name, def_val, unit = result.groups()
return MemberVariable(name=name, type=klass, unit=unit, default_val=def_val)

def parse(self, string, require_description=True):
"""Parse the passed string"""
Expand Down
11 changes: 11 additions & 0 deletions python/podio/test_MemberParser.py
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,17 @@ def test_parse_valid_no_description(self):
self.assertEqual(parsed.description, 'descriptions are not ignored even though they are not required')
self.assertTrue(not parsed.is_builtin)

def test_parse_unit(self):
"""Test that units are properly parsed"""
parser = MemberParser()

parsed = parser.parse('unsigned long long var [GeV] // description')
self.assertEqual(parsed.unit, 'GeV')

hegner marked this conversation as resolved.
Show resolved Hide resolved
parsed = parser.parse('unsigned long long var{42} [GeV] // description')
self.assertEqual(parsed.unit, 'GeV')
self.assertEqual(parsed.default_val, '42')

def test_string_representation(self):
"""Test that the string representation that is used in the jinja2 templates
includes the default initialization"""
Expand Down
16 changes: 8 additions & 8 deletions python/templates/macros/declarations.jinja2
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,15 @@

{% macro member_getters(members, get_syntax) %}
{%for member in members %}
/// Access the {{ member.description }}
/// Access the {{ member.docstring }}
const {{ member.full_type }}& {{ member.getter_name(get_syntax) }}() const;
{% if member.is_array %}
/// Access item i of the {{ member.description }}
/// Access item i of the {{ member.docstring }}
const {{ member.array_type }}& {{ member.getter_name(get_syntax) }}(size_t i) const;
{%- endif %}
{% if member.sub_members %}
{% for sub_member in member.sub_members %}
/// Access the member of {{ member.description }}
/// Access the member of {{ member.docstring }}
const {{ sub_member.full_type }}& {{ sub_member.getter_name(get_sytnax) }}() const;
{% endfor %}
{% endif %}
Expand All @@ -27,24 +27,24 @@

{% macro member_setters(members, get_syntax) %}
{% for member in members %}
/// Set the {{ member.description }}
/// Set the {{ member.docstring }}
void {{ member.setter_name(get_syntax) }}({{ member.full_type }} value);
{% if member.is_array %}
void {{ member.setter_name(get_syntax) }}(size_t i, {{ member.array_type }} value);
{% endif %}
{% if not member.is_builtin %}
/// Get reference to {{ member.description }}
/// Get reference to {{ member.docstring }}
{{ member.full_type }}& {{ member.name }}();
{% endif %}
{% if member.sub_members %}
{% for sub_member in member.sub_members %}
{% if sub_member.is_builtin %}
/// Set the member of {{ member.description }}
/// Set the member of {{ member.docstring }}
void {{ sub_member.setter_name(get_syntax) }}({{ sub_member.full_type }} value);
{% else %}
/// Get reference to the member of {{ member.description }}
/// Get reference to the member of {{ member.docstring }}
{{ sub_member.full_type }}& {{ sub_member.name }}();
/// Set the member of {{ member.description }}
/// Set the member of {{ member.docstring }}
void {{ sub_member.setter_name(get_sytnax) }}({{ sub_member.full_type }} value);
{% endif %}
{% endfor %}
Expand Down
18 changes: 9 additions & 9 deletions tests/datalayout.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ options :
components :
SimpleStruct:
Members:
- int x
- int y
- int z
- std::array<int, 4> p
- int x [mm]
- int y [mm]
- int z [mm]
- std::array<int, 4> p [mm]
# can also add c'tors:
ExtraCode :
declaration: "
Expand All @@ -26,7 +26,7 @@ components :
Description : "A not so simple struct"
Author : "Someone"
Members:
- SimpleStruct data // component members can have descriptions
- SimpleStruct data [GeV] // component members can have descriptions and units

ex2::NamespaceStruct:
Members:
Expand Down Expand Up @@ -66,10 +66,10 @@ datatypes :
Author : "B. Hegner"
Members:
- unsigned long long cellID // cellID
- double x // x-coordinate
- double y // y-coordinate
- double z // z-coordinate
- double energy // measured energy deposit
- double x [mm] // x-coordinate
- double y [mm] // y-coordinate
- double z [mm] // z-coordinate
- double energy [GeV] // measured energy deposit

ExampleMC :
Description : "Example MC-particle"
Expand Down