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

feat: add optional custom print callable #2121

Merged
merged 5 commits into from
Mar 25, 2024

Conversation

pya
Copy link
Contributor

@pya pya commented Mar 5, 2024

Objective

The printout of a MF6 run can be pretty long, especially if there are many time steps. While it is possible turn off printing altogether, it can be useful to allow the user to customize the printing. This PR suggest to add a callable the will be use during the model instead of the builtin print function.

Example: Print time steps on top of each other

Setting up a model:

import flopy

sim_name = 'AdvGW_tidal' 
sim_ws = 'examples/data/mf6/test005_advgw_tidal'

sim = flopy.mf6.MFSimulation.load(
    sim_name=sim_name,
    exe_name=exe_name,  # not shown here
    sim_ws=sim_ws,
)

Defining a custom print callable:

from time import sleep

class CustomPrint:
    """
    Custom print of MF6 output.

    This prints the line containing 'Solving:' in place, i.e. creating
    an up-counting effect for the number of the time step. The last time
    step in each stress period will remain visible and the print of the 
    next stress period will continue on the next line.
    """

    def __init__(self):
        self.one_line_mode = False
        self.last_stress_period = 0
        
    def __call__(self, line):
        if 'executable' in line:
            return
        if 'Solving:' in line:
            stress_period = int(line.split('Stress period:',)[1].split()[0])
            if stress_period > self.last_stress_period:
                self.last_stress_period += 1
                print()
            # don't add a line break to create up-counting number effect
            print(line, end='\r')
            self.one_line_mode = True
            # artificially slow done printing to see the effect
            sleep(0.1)
        else:
            if self.one_line_mode:
                self.one_line_mode = False
                # add line break
                print()
            print(line)

Running the model with the custom printing:

sim.run_simulation(custom_print=CustomPrint())

Yields this output:

 MODFLOW 6
                U.S. GEOLOGICAL SURVEY MODULAR HYDROLOGIC MODEL
                        VERSION 6.4.1 Release 12/09/2022

        MODFLOW 6 compiled Feb 12 2023 12:41:20 with GCC version 11.3.0

This software has been approved for release by the U.S. Geological 
Survey (USGS). Although the software has been subjected to rigorous 
review, the USGS reserves the right to update the software as needed 
pursuant to further analysis and review. No warranty, expressed or 
implied, is made by the USGS or the U.S. Government as to the 
functionality of the software and related material nor shall the 
fact of release constitute any such warranty. Furthermore, the 
software is released on condition that neither the USGS nor the U.S. 
Government shall be held liable for any damages resulting from its 
authorized or unauthorized use. Also refer to the USGS Water 
Resources Software User Rights Notice for complete use, copyright, 
and distribution information.


 Run start date and time (yyyy/mm/dd hh:mm:ss): 2024/03/05 18:27:16

 Writing simulation list file: mfsim.lst
 Using Simulation name file: mfsim.nam


    Solving:  Stress period:     1    Time step:     1
    Solving:  Stress period:     2    Time step:   120
    Solving:  Stress period:     3    Time step:   120
    Solving:  Stress period:     4    Time step:   120

 Run end date and time (yyyy/mm/dd hh:mm:ss): 2024/03/05 18:27:17
 Elapsed run time:  0.242 Seconds

 Normal termination of simulation.

Over time the output develops like this:

    Solving:  Stress period:     1    Time step:     1
    Solving:  Stress period:     2    Time step:    63

A little bit later:

    Solving:  Stress period:     1    Time step:     1
    Solving:  Stress period:     2    Time step:   120
    Solving:  Stress period:     3    Time step:     5

Of course other print variations are possible.

Impact

The impact on the code is minimal. Essentially handing through a custom callable and using it instead of print will do. No changes in behavior will occur if no callable is supplied.

Testing

Running pytest yields the same result as without the changes.

Copy link

codecov bot commented Mar 5, 2024

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 73.1%. Comparing base (43e5178) to head (e319098).

Additional details and impacted files
@@           Coverage Diff           @@
##           develop   #2121   +/-   ##
=======================================
  Coverage     73.1%   73.1%           
=======================================
  Files          259     259           
  Lines        59526   59529    +3     
=======================================
+ Hits         43531   43534    +3     
  Misses       15995   15995           
Files Coverage Δ
flopy/mbase.py 70.0% <100.0%> (+0.1%) ⬆️
flopy/mf6/mfsimbase.py 68.0% <ø> (ø)

@pya
Copy link
Contributor Author

pya commented Mar 7, 2024

Looks like the check "FloPy documentation / Prepare and test notebooks" timed out on Linux and MacOS. On Windows it takes 23m 22s but on Linux and MacOS it takes 6h 0m 16s. Both show the error message Error: The operation was canceled.

@pya pya changed the title Add optional custom print callable feat: add optional custom print callable Mar 10, 2024
Copy link
Member

@wpbonelli wpbonelli left a comment

Choose a reason for hiding this comment

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

Thank you @pya, I like this feature. The CI issue is resolved but maybe best to get some more buy-in before merging. @langevin-usgs @jdhughes @jlarsen-usgs @spaulins-usgs

flopy/mbase.py Outdated
Optional callbale for printing. It will replace the builtin
print function. This is useful for shorter prints or integration
into other systems such as GUIs.
default is None, i.e. use the builtion print
Copy link
Member

Choose a reason for hiding this comment

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

Just some typos

  • callbale -> callable
  • builtion -> builtin

Maybe also "shorter prints" -> "shorter output"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@wpbonelli Thanks for finding my typos. I guess, I should use a spell checker in my editor. ;)

I am not sure about the wording. "shorter output" seems a bit too generic. I would also associate something like file output with it. How about "shorter print output"?

Copy link
Member

Choose a reason for hiding this comment

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

I think "shorter print output" makes sense, or it could be merged as is if you prefer the first wording. I am happy to fix typos and merge, unless you are on it already?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I fixed the typos and pushed my changes. Now ruff died on the test file, which did not touch.


assert success
assert any(buff)
assert any(ws.glob("*.lst"))
Copy link
Member

Choose a reason for hiding this comment

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

Should we check buff is the same when custom_print=print and when not explicitly provided? Maybe also good to check that buff changes with a custom callable.

Copy link
Contributor

@langevin-usgs langevin-usgs left a comment

Choose a reason for hiding this comment

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

Thanks @pya. Seems like a good addition to me.

@wpbonelli
Copy link
Member

@pya rebasing on develop and running ruff format . from the project root should fix the format error. We switched to ruff from black/isort/etc since you created this PR, sorry about that.

After some more thought, I wonder about the consequences of reassigning print. I don't think we want to permanently alter the behavior of a builtin function — is there a way to alter its behavior only within flopy?

Apology for the delay, I thought I commented to the same effect last week, either I never clicked the button or it disappeared in the ether

@pya
Copy link
Contributor Author

pya commented Mar 25, 2024

@wpbonelli Switching to ruff is a good thing. I just included a chapter about ruff in my teaching portfolio. I taught a short session about it last December.

As for reassigning print, I don't. There is no effect outside the function run_model. This is the relevant code:

def run_model(
   ...
    custom_print=None,
) -> Tuple[bool, List[str]]:
    if custom_print is not None:
        print = custom_print
    else:
        print = __builtins__["print"]

I create a new local variable named print that is an alias for either a supplied callable or the builtin function print. pylint would have warned if I had overridden a builtin function. ;)

If you don't like the name print use a different name for example run_print:

def run_model(
   ...
    custom_print=None,
) -> Tuple[bool, List[str]]:
    if custom_print is not None:
        run_print = custom_print
    else:
        run_print = __builtins__["print"]

Now, use run_print instead of print for the rest of the function.

@pya
Copy link
Contributor Author

pya commented Mar 25, 2024

@wpbonelli I rebased on develop with git rebase develop -i as described in CONTRIBUTING.md. This seemed to be successful: Successfully rebased and updated refs/heads/feature/custom-print.. But running ruff format . modified 416 files. I don't think these formatting changes should be part of my PR. What am I doing wrong? Or is this ok?

@wpbonelli
Copy link
Member

As for reassigning print, I don't. There is no effect outside the function run_model. ... I create a new local variable named print that is an alias for either a supplied callable or the builtin function print. pylint would have warned if I had overridden a builtin function. ;)

Very nice, my mistake- I misunderstood what was happening. Thank you for educating me :)

But running ruff format . modified 416 files. I don't think these formatting changes should be part of my PR. What am I doing wrong? Or is this ok?

It sounds like your develop may be out of date with the upstream, the tip is currently 43e5178. Maybe solved by fetching latest, updating local develop, then rebasing again?

I tried locally and after rebasing, ruff format . just reformats test_mbase.py as expected. Let me know if you'd like me to push this final fix. Either way, after it's in, we can merge straightaway.

@pya
Copy link
Contributor Author

pya commented Mar 25, 2024

Thanks for the hint. I updated my branch develop on GitHub, pulled it locally, and rebased my feature branch with it. Now, ruff modifies only one file. I still need to get used to the workflow. ;)

@wpbonelli wpbonelli merged commit f75853f into modflowpy:develop Mar 25, 2024
24 checks passed
@wpbonelli
Copy link
Member

Thanks again @pya for this contribution

@pya
Copy link
Contributor Author

pya commented Mar 26, 2024

Thanks @wpbonelli for walking me through the process.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants