Skip to content

Conversation

@MattToast
Copy link
Member

Creates a basic implementation for a CompundEntity base class that can be used to build a launchable containing multiple processes (Jobs, JobGroup, etc) each with their own settings from a single LaunchSettings instance.

This class is then subclassed to create an Ensemble class that mimics the original implementation: I takes in an Application (Model) and then maps settings/params/input files over the collection before creating the launchable jobs.

@codecov
Copy link

codecov bot commented May 29, 2024

Codecov Report

Attention: Patch coverage is 45.21739% with 63 lines in your changes missing coverage. Please review.

Please upload report for BASE (smartsim-refactor@7e3e92c). Learn more about missing BASE report.

Additional details and impacted files

Impacted file tree graph

@@                 Coverage Diff                  @@
##             smartsim-refactor     #605   +/-   ##
====================================================
  Coverage                     ?   33.26%           
====================================================
  Files                        ?      107           
  Lines                        ?     6401           
  Branches                     ?        0           
====================================================
  Hits                         ?     2129           
  Misses                       ?     4272           
  Partials                     ?        0           
Files Coverage Δ
smartsim/_core/generation/generator.py 25.80% <ø> (ø)
smartsim/_core/utils/helpers.py 32.16% <ø> (ø)
smartsim/experiment.py 30.12% <ø> (ø)
smartsim/launchable/job.py 70.83% <100.00%> (ø)
smartsim/launchable/mpmdjob.py 40.00% <ø> (ø)
smartsim/launchable/mpmdpair.py 75.00% <100.00%> (ø)
smartsim/entity/entity.py 61.11% <83.33%> (ø)
smartsim/entity/model.py 32.00% <0.00%> (ø)
smartsim/entity/_mock.py 75.00% <75.00%> (ø)
smartsim/entity/ensemble.py 44.11% <42.42%> (ø)
... and 1 more

Copy link
Contributor

@amandarichardsonn amandarichardsonn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice work!!

Copy link
Contributor

@amandarichardsonn amandarichardsonn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Lets go! Awesome work and thank you for fixing those type errors, some suggestions but feel free to say nah

copy.deepcopy(exe_arg_parameters) if exe_arg_parameters else {}
)
self.files = copy.deepcopy(files) if files else EntityFiles()
self.file_parameters = dict(file_parameters) if file_parameters else {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you mind adding a deep_copy for the file_parameters, just to be consistent with exe_arg_parameters? That is a my bad MERP

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What are your thoughts on this, I am thinking that the attributes that do not need to be deepcopied are replicas, max_permutations and strategy, just bc they do not live on to Application and are not reused

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

or hmm maybe deepcopy is just something to worry about for Application?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the general thought process is that we should make copies of anything that is mutable when it is past to the ensemble constructor.

Because strings are immutable in python, a deep-copy of a dict[str, str] (or really any MutableMapping[str, str]) is functionally equivalent to making a shallow copy! I chose dict over copy.copy or copy.deepcopy simply so that any helper methods know the actual type of the container, and so that user know they could modify the collection after construction (should they choose)

original_params = {"SOME": "PARAM"}
ens = Ensemeble(..., file_parameters=original_params, ...)

# Wait I need to change those params for some reason!!
# But know that I can because the attr is typed as a dict!
ens.file_parameters["SOME"] = "OTHER_PARAMETER"  # passes type check!
assert ens.file_parameters != original_params    # passes!

Copy link
Member Author

@MattToast MattToast Jul 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To that end, I think we need:

  • a deep copy of files as its a complex user (SmartSim) defined class
  • a deep copy of exe_args_parameters as its a container of containers of immutable elements
  • shallow copies of file_parameters and exe_args as they are containers of immutable elements
  • No copies of any other parameters as they are all immutable

What do you think?

@MattToast MattToast force-pushed the smartsim-refactor branch 2 times, most recently from d15e951 to e9d8eca Compare June 26, 2024 22:57
# self.exe = [exe] if run_settings.container else [expand_exe_path(exe)]
self.exe_args = exe_args or []
self.params = params or {}
self.params = params.copy() if params else {}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why shallow copy of params here? and not deep? but deep for files?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strings are immutable in python, a shallow copying is container of immutable objects is functionally equivalent to making deep copy because in order to change any of elements in the container it must be replaced wholesale!

See here for my decision process for what I chose to deep/shallow/not copy, but I could be very easily convinced to make this a deep copy just for consistencies stake if you'd like!

new_strat = lambda params, exe_args, nmax: []
strategies._register("new_strat")(new_strat)
assert strategies._REGISTERED_STRATEGIES == {"new_strat": new_strat}

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just to check my understanding!

Here you first make sure that _REGISTERED_STRAT is empty. next you define a lambda function (easier than actually writing a user function) then you register it. But it seems kinda funny to do ._register("new_strat")(new_strat) - why the two ()() when _register just accpets (str)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

^^ Exactly correct!!

The reason for the two () calls is because register is a callable that returns a callable that actually does the registration. The registration is actually done during the second call. You can think of it as actually doing something like this

_tmp_fn_to_add_a_new_strategy_under_the_name_new_strategy = strategies._register("new_strat")

reveal_type(_tmp_fn_to_add_a_new_strategy_under_the_name_new_strategy)
# reveals: `t.Callable[[PermutationStrategyType], PermutationStrategyType]`

assert strategies._REGISTERED_STRATEGIES == {}
# Passes! Notice that nothing has been added yet

new_strat = lambda p, e, n: []
return_from_tmp_fn = _tmp_fn_to_add_a_new_strategy_under_the_name_new_strategy(new_strat)

assert return_from_tmp_fn is new_strat
# Passes! Notice that the function is returned unchanged

assert strategies._REGISTERED_STRATEGIES == {"new_strat": new_strat}
# Passes! Notice that the key was only added after
# `_tmp_fn_to_add_a_new_strategy_under_the_name_new_strategy` was called

All of this is for the weird magic that let's us use this function like a decorator. This test is functionally testing this use case

stategies._register("new_strat")
def new_strat(p, e, n):
    return []

Copy link
Contributor

@amandarichardsonn amandarichardsonn left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM just 2 Qs

@MattToast MattToast merged commit c2164ca into CrayLabs:smartsim-refactor Jul 10, 2024
amandarichardsonn added a commit to amandarichardsonn/SmartSim that referenced this pull request Jul 17, 2024
Creates a basic implementation for a `CompundEntity` base class that can
be used to build a launchable containing multiple processes (`Job`s,
`JobGroup`, etc) each with their own settings from a single
`LaunchSettings` instance.

This class is then subclassed to create an `Ensemble` class that mimics
the original implementation: I takes in an `Application` (`Model`) and
then maps settings/params/input files over the collection before
creating the launchable jobs.

[ committed by @MattToast ]
[ reviewed by @amandarichardsonn ]

---------

Co-authored-by: amandarichardsonn <[email protected]>
amandarichardsonn added a commit to amandarichardsonn/SmartSim that referenced this pull request Jul 17, 2024
Creates a basic implementation for a `CompundEntity` base class that can
be used to build a launchable containing multiple processes (`Job`s,
`JobGroup`, etc) each with their own settings from a single
`LaunchSettings` instance.

This class is then subclassed to create an `Ensemble` class that mimics
the original implementation: I takes in an `Application` (`Model`) and
then maps settings/params/input files over the collection before
creating the launchable jobs.

[ committed by @MattToast ]
[ reviewed by @amandarichardsonn ]

---------

Co-authored-by: amandarichardsonn <[email protected]>
@MattToast MattToast deleted the compound-entity-proto branch August 9, 2024 23:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants