Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
b27a8e2
refactor to isolate retrieving full json dict of moose objs
helen-brooks Nov 27, 2023
72d3bf0
add a method to organise all MOOSE block types
helen-brooks Nov 27, 2023
e1a8ad5
generalise object extraction to any fundamental type (not nested)
helen-brooks Nov 27, 2023
90905ff
generalise object extraction to any nested block type
helen-brooks Nov 27, 2023
de35eba
collect objects in a MOOSE model
helen-brooks Nov 27, 2023
40d4855
add property to contain list of all moose params
helen-brooks Nov 29, 2023
cca96ca
enable printing
helen-brooks Nov 29, 2023
7de02d1
do not use a dictionary in moose model
helen-brooks Nov 29, 2023
67b529a
Added a factory
helen-brooks Nov 29, 2023
1678121
now support nested types like variables
helen-brooks Nov 30, 2023
eb660f2
Store extra metadata in a MooseParam so we can check default vals, pr…
helen-brooks Dec 1, 2023
6efa8d0
get documentation working
helen-brooks Dec 1, 2023
d0e81a0
WIP attempt to add non-typed system syntax
helen-brooks Dec 4, 2023
83a19d0
add optional arugment print_default in to_str methods
helen-brooks Dec 7, 2023
b7c5d17
update to gitignore
helen-brooks Dec 7, 2023
c058710
add more syntax types
helen-brooks Dec 8, 2023
2c91218
Too many edge cases - find parameters through recursion instead
helen-brooks Dec 8, 2023
15765e1
create syntax blocks via a registry
helen-brooks Dec 8, 2023
9456c5e
construct syntax registry from an executable path
helen-brooks Dec 11, 2023
bdc3ed3
WIP: update factory to use syntax registry
helen-brooks Dec 11, 2023
82edb83
WIP: continue generalising factory to more syntax
helen-brooks Dec 11, 2023
e5dfdcb
refactoring - separate Factory class out
helen-brooks Dec 12, 2023
21fad0b
refactoring - separate Syntax classes out
helen-brooks Dec 12, 2023
a77fffc
refactoring - separate Syntax classes out
helen-brooks Dec 12, 2023
d9e0c45
refactoring - move parsing functions from cbird to syntax
helen-brooks Dec 12, 2023
2e76a25
Add docstrings
helen-brooks Dec 12, 2023
d5d6e7a
refactoring - move now obselete functions from cbird into legacy. hop…
helen-brooks Dec 12, 2023
907a3e5
now support mixins in moose model basic syntax
helen-brooks Dec 12, 2023
65e3f81
More consistent naming schema
helen-brooks Dec 12, 2023
a2aeeba
Rename file cbird to obj for more consistent module naming
helen-brooks Dec 12, 2023
5b8dd3c
change attribute names to be more logical
helen-brooks Dec 12, 2023
143b63b
just one loop over available syntax in factory
helen-brooks Dec 13, 2023
7bf00ca
factory now stores constructors using relation as key
helen-brooks Dec 13, 2023
a09bf9c
add mechanism to save nested syntax in factory
helen-brooks Dec 13, 2023
a372d48
nested syntax may now be added in model
helen-brooks Dec 13, 2023
fdd48b4
Object + action mixins working
helen-brooks Dec 13, 2023
4bcd943
keep track of object and action params seperately
helen-brooks Dec 14, 2023
db7a74d
to_str method is working again for non-nested
helen-brooks Dec 14, 2023
35bb7e5
printing works for collections and mixins
helen-brooks Dec 14, 2023
5fe999a
Adding objects to collections works again
helen-brooks Dec 19, 2023
aa8190f
some tidying, restore model functionality
helen-brooks Dec 19, 2023
a57d3c5
restore capability to write model to file; add kwargs to variables
helen-brooks Dec 19, 2023
0d3fc4e
undo accidental change to collection.add
helen-brooks Dec 19, 2023
1772207
missing error message
helen-brooks Dec 20, 2023
1a622ed
WIP. Attempt to prevent static class creation
helen-brooks Dec 20, 2023
51070df
Documentation fixed
helen-brooks Jan 3, 2024
1514482
Some refactoring and tidying
helen-brooks Jan 3, 2024
cb00d4f
Documentation works for MooseCollection objects
helen-brooks Jan 3, 2024
16cd4fd
Re-enable support for factory config file
helen-brooks Jan 3, 2024
239fbd8
fix mix-in initialisation
helen-brooks Jan 4, 2024
3376753
fix bugs lifting syntax from json tree, and when setting attributes a…
helen-brooks Feb 6, 2024
9d3a15f
support mixin types as leaf nodes
helen-brooks Feb 6, 2024
58bb98e
avoid syntax collisions for mixins
helen-brooks Feb 6, 2024
5af584f
better handling of default values, indents, and type-casting of attri…
helen-brooks Feb 7, 2024
774c5bf
Fix boilerplate mixin list for objects to be added to collections
helen-brooks Feb 9, 2024
7811146
add support for mesh generators
helen-brooks Feb 9, 2024
dd4471c
Add a working thermal example
helen-brooks Feb 9, 2024
a1c16d3
refuse to print duplicated params, and enable print_defaults during f…
helen-brooks Feb 9, 2024
30968bd
Update README
helen-brooks Feb 9, 2024
9c891a6
tweaks to README
helen-brooks Feb 9, 2024
f8ddfae
add pshriwise suggestion to json reader from code review
helen-brooks Feb 27, 2024
dc92148
update thermal example to use up-to-date module name
helen-brooks Feb 27, 2024
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
__pycache__
*~
*#
build
catbird.egg-info
127 changes: 80 additions & 47 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,6 @@ Catbird

A code for generation of Python objects and input for various [MOOSE](https://moose.inl.gov/SitePages/Home.aspx).

*Caveats: This currently only parses data for the `MOOSE::Problem` type.*

Prerequisites
-------------

Expand All @@ -29,63 +27,98 @@ To include packages for testsing, run
pip install .[test]
```

Example
-------
# Usage

Below is an example generating Python classes for the `OpenMCCellAverageProblem` and `NekRSProblem` types in the [Cardinal](https://cardinal.cels.anl.gov/).
## Load syntax into a Factory
First, import the package and load available syntax from a MOOSE application.

```python
In [1]: from catbird import app_from_exec
>>> from catbird import *
>>> factory=Factory('./heat_conduction-opt')
```
This may take a few minutes, so don't panic if it hangs. The syntax first has to be dumped from the original application as json and then parsed;
the factory then converts "available" syntax into python object constructors. The more syntax we enable the longer it takes, so if you want
to speed things up you should only enable the syntax you intend to use. To write out what objects are currently enabled, try:
```python
>>> factory.write_config("config.json")
```
We could then edit that file and load it via an optional constructor argument to `Factory`:
```python
>>> lightweight_factory=factory=Factory('./heat_conduction-opt', config="lighweight_config.json")
```
However, modifying the config file directory is likely to be very laborious if done manually. Another option
is to derive your own Factory class and override the `set_defaults` method. To limit the enabled syntax, we can pass in dictionaries
of enabled syntax into the `enable_syntax` method, e.g.:
```
executioner_enable_dict={
"obj_type": ["Steady","Transient"]
}
self.enable_syntax("Mesh")
self.enable_syntax("Executioner", executioner_enable_dict)
```

In [2]: cardinal = app_from_exec('./cardinal-opt')
## Create a Model
With our factory built, all we have in practice is bunch of constructor objects. We now need to create the objects and assemble them as a `Model`:
```python
>>> model=MooseModel(factory)
```
This will have created a "boiler-plate" model with some sensible base objects to work with. We can now start to set their attributes in an object-oriented way.
```
>>> model.mesh.dim=2
```
To see the available attributes for a given object, just use "help" to obtain useful documentation e.g.:
```
>>> help(model.mesh)
```
The help will also print the type and valid options. Type checking of attributes is performed, and if the type (i) incompatible with the expected type
and (ii) not castable as the expected type then a ValueError exception is raised, e.g.
```
>>> model.mesh.dim='2' # This is fine, MOOSE expects string
>>> model.mesh.dim=2 # This is fine, str(2) is valid
>>> model.mesh.nx=1 # This is fine, MOOSE expects in
>>> model.mesh.nx="hello" # This raises a ValueError as int("hello") is invalid
```

In [3]: openmc_prob = cardinal['problems']['OpenMCCellAverageProblem']()
Many of the objects are `Collection` types, i.e. a collection MOOSE objects, for example Variables. To add a variable, we must call:
```
>>> model.add_variable("T", order="SECOND")
```
Notice the use of key-word arguments in this function call. We can also act on the created object directly, via
```
>>> model.add_variable("T")
>>> model.variables.objects["T"].order="SECOND"
```

In [4]: openmc_prob.batches?
Type: property
String form: <property object at 0x12906ec50>
Docstring:
Type: int
Number of batches to run in OpenMC; this overrides the setting in the XML files.
Even if the root-level syntax hasn't been added to the model (but is available in the factory) we can still add it to our model through `add_syntax` calls:
```
model.add_syntax("VectorPostprocessors")
```
Since this is a Collection type, to add a specific VectorPostprocessor, we call the add_to_collection method:
```
model.add_to_collection("VectorPostprocessors","VectorPostprocessor","t_sampler")
```

Features
--------
It is generally expected that the user will develop their own class derived from `MooseModel`, overriding the method `load_default_syntax`.
See for example TransientModel in `model.py`.

Every attribute comes with type and dimensionality checking (for array types).
This provides live feedback to users when setting problem parameters. In the example above,
setting `openmc_prob.batches` to a non-integer value results in the following

## Write to file
Finally if we are happy with our model we can write to file:
```python
In [5]: openmc_prob.batches = 100

In [6]: openmc_prob.batches = 'one hundred'
---------------------------------------------------------------------------
ValueError Traceback (most recent call last)
Cell In[6], line 1
----> 1 openmc_prob.batches = 'one hundred'

File ~/soft/catbird/catbird/cbird.py:66, in Catbird.prop_set.<locals>.fset(self, val)
64 def fset(self, val):
65 if dim == 0:
---> 66 self.check_type(name, val, attr_type)
67 if allowed_vals is not None:
68 self.check_vals(name, val, allowed_vals)

File ~/soft/catbird/catbird/cbird.py:40, in Catbird.check_type(name, val, attr_type)
38 val_type_str = val.__class__.__name__
39 exp_type_str = attr_type.__name__
---> 40 raise ValueError(f'Incorrect type "{val_type_str}" for attribute "{name}". '
41 f'Expected type "{exp_type_str}".')
42 return val
>>> model.write("catbird_input.i")
```
You can run that input from the command line as you normally would or launch a subprocess.

ValueError: Incorrect type "str" for attribute "batches". Expected type "int".
Note, if you inspect our file `catbird_input.i` you may not see all attributes written out. The default behaviour is that parameters that are unchanged relative to
their default value are suppressed when writing to file. To see all attributes, instead run
```python
>>> model.write("catbird_input_full.i", print_default=True)
```

Default values are also set on attributes automatically, so full problem
descriptions can be generated by setting only required parameters.
Example
-------
A fully worked heat conduction example may be found in: `catbird/examples/thermal.py`. Run with
```bash
$ python examples/thermal.py
```

```python
In [7]: openmc_prob.initial_properties
Out[7]: 'moose'
```
6 changes: 3 additions & 3 deletions catbird/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@


from .cbird import *
from .obj import *
from .model import *
from .collection import *
38 changes: 38 additions & 0 deletions catbird/action.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
from .base import MooseBase

class MooseAction(MooseBase):
params_name="_moose_action_params"
class_alias="Action"

def __init__(self):
if hasattr(self,"_action_moose_params"):
# Dictionary of the attributes this class should have
moose_param_dict_local=getattr(self,"_moose_action_params")

# Loop over and make into attributes
for attr_name, moose_param in moose_param_dict_local.items():
# Crucially, acts on the instance, not the class.
setattr(self,attr_name,moose_param.val)

@property
def moose_action_params(self):
"""
Return a unified list of all the parameters we've added.
"""
moose_param_list_local=[]
if hasattr(self,"_moose_action_params"):
moose_param_dict_local=getattr(self,"_moose_action_params")
moose_param_list_local=list(moose_param_dict_local.keys())
return moose_param_list_local

def inner_to_str(self,print_default=False):
inner_str=""
param_list=self.moose_action_params

# We don't print the type of actions
if "type" in param_list:
param_list.remove("type")

for attr_name in param_list:
inner_str+=self.attr_to_str(attr_name,print_default)
return inner_str
148 changes: 148 additions & 0 deletions catbird/base.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,148 @@
from abc import ABC
from collections.abc import Iterable
from .param import MooseParam
from .string import MooseString

class MooseBase(ABC,MooseString):
"""
Class that can add type-checked properties to itself.
"""
def __setattr__(self, attr_name, value_in):
value_to_set=value_in
if hasattr(self,attr_name):
attr_val_now=getattr(self,attr_name)
type_now=type(attr_val_now)

sub_type_now=None
if issubclass(type_now, Iterable) and type_now != str :
sub_type_now=type(attr_val_now[0])

if not isinstance(value_in,type_now):
# If not the right type, try to cast
values=None
if isinstance(value_in,str):
values=value_in.split()

try:
if sub_type_now is not None:
if values:
value_to_set=[ sub_type_now(v) for v in values ]
else:
value_to_set=[sub_type_now(value_in)]
else:
value_to_set=type_now(value_in)
except ValueError:
msg="Attribute {} should have type {}".format(attr_name,type_now)
raise ValueError(msg)
super().__setattr__(attr_name, value_to_set)


@staticmethod
def check_type(name, val, attr_type):
"""Checks a value's type"""
if not isinstance(val, attr_type):
val_type_str = val.__class__.__name__
exp_type_str = attr_type.__name__
raise ValueError(f'Incorrect type "{val_type_str}" for attribute "{name}". '
f'Expected type "{exp_type_str}".')
return val

@staticmethod
def check_vals(name, val, allowed_vals):
"""Checks that a value is in the set of allowed_values"""
if val not in allowed_vals:
raise ValueError(f'Value {val} for attribute {name} is not one of {allowed_vals}')

@classmethod
def add_moose_param(cls,moose_param):
assert isinstance(moose_param,MooseParam)

# Check name
attr_name=moose_param.name
if attr_name.find("_syntax_") != -1:
msg="'_syntax_' is reserved attribute string. Cannot create attibute {}".format(attr_name)
raise RuntimeError(msg)

# Store attribute in dict
params_name=None
if cls.params_name:
if not hasattr(cls,cls.params_name):
setattr(cls,cls.params_name,{})
moose_param_dict_local=getattr(cls,cls.params_name)
moose_param_dict_local[attr_name]=moose_param
setattr(cls,cls.params_name,moose_param_dict_local)

def get_param(self,attr_name):
"""Return MooseParam corresponding to attribute name.

Raise KeyError if not found.
"""
dict_now=getattr(self,self.params_name)
return dict_now[attr_name]

def is_default(self,attr_name):
# Get current value
attr_val = getattr(self, attr_name)

# Look up the default value
param=self.get_param(attr_name)
default_val = param.default
if default_val is None:

default_val = param.attr_type()

# Compare and return
return attr_val == default_val

def attr_to_str(self,attr_name,print_default=False):
attr_str=""
if self.is_default(attr_name) and not print_default:
return attr_str

attr_val=getattr(self, attr_name)
if attr_val is None:
return attr_str

type_now=type(attr_val)
attr_val_str=""
if issubclass(type_now, Iterable) and type_now != str :
str_list = [ str(v)+" " for v in attr_val ]
attr_val_str="".join(str_list)
attr_val_str=attr_val_str.rstrip()
attr_val_str="'"+attr_val_str+"'"
else:
attr_val_str=str(attr_val)

attr_str=self.indent+'{}='.format(attr_name)+attr_val_str+'\n'
return attr_str

@classmethod
def moose_doc(cls, param_list):
"""Generate documentation for all the MOOSE parameters"""
# Obtain class info for header
class_string=''
if hasattr(cls,'class_alias'):
class_name_now=getattr(cls,'class_alias')
class_string=" "+class_name_now
param_string='MOOSE'+class_string+' Parameters'
dash_len=len(param_string)
dashes=''.ljust(dash_len,"-")

# Write header
doc_now=param_string
doc_now+="\n"+dashes

# Loop over parameters' documentation
for param in param_list:
assert isinstance(param,MooseParam)
# We don't document the type as it is fixed
if param.name == "type":
continue
doc_now=doc_now+param.doc

# Footer
more_dashes=''.ljust(65,"-")
doc_now+=more_dashes
doc_now+="\n"

return doc_now
Loading