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

Potential API for PyGFunction #4

Open
wants to merge 40 commits into
base: master
Choose a base branch
from
Open

Potential API for PyGFunction #4

wants to merge 40 commits into from

Conversation

vtnate
Copy link
Collaborator

@vtnate vtnate commented Nov 1, 2024

This is an example showing how to pass purely data (floats) to PyGFunction and have it return a gfunction in list form.

There are lots of commented lines in this first draft, while we explore various techniques for achieving a simpler use of PyGFunction in GHED.

@vtnate vtnate added the enhancement New feature or request label Nov 1, 2024
@vtnate vtnate requested a review from mitchute November 1, 2024 22:32
@vtnate vtnate self-assigned this Nov 1, 2024
pygfunction/api.py Outdated Show resolved Hide resolved
@vtnate vtnate marked this pull request as ready for review November 6, 2024 17:33
@mitchute
Copy link
Owner

mitchute commented Nov 8, 2024

@Myoldmopar can you take a look here? IMO this is headed in the right direction. We may want to see about some more helper methods for creating the lists of borehole params, or maybe we just leave that to the user?

pygfunction/api.py Outdated Show resolved Hide resolved
tests/test_api.py Outdated Show resolved Hide resolved
Comment on lines 25 to 36
for borehole_config_items in borehole_config_list:
_borehole = Borehole(*borehole_config_items)
borefield.append(_borehole)

self.gfunc = gFunction(
borefield,
alpha,
time=time,
boundary_condition=boundary_condition,
options=options,
method=solver_method,
)
Copy link
Owner

Choose a reason for hiding this comment

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

@Myoldmopar responding to this comment (https://github.com/mitchute/pygfunction/pull/4/files#r1834563947) the gFunction class needs a list of Borehole class instances. All this is doing is setting up the init arguments which that class needs. Those parameters are here: https://github.com/mitchute/pygfunction/pull/4/files#diff-3451cc474d5698222168f66a293339a6bd296ee2c56f56409866bbae99917a79R25

pygfunction does have some methods for building out these parameter lists for some GHE configurations, so maybe we can offload some of that there. For custom GHE configurations, I think we'll likely end up doing a lot of this ourselves.

Copy link
Collaborator

@Myoldmopar Myoldmopar left a comment

Choose a reason for hiding this comment

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

@vtnate @mitchute

I made a few more comments on here. You are welcome to reject them if you feel they aren't valid. And also, I'm not intending on adding work to either of you, so if they are valid comments but you don't have time, I can jump in and get to work on them myself.

pygfunction/api.py Outdated Show resolved Hide resolved
from typing import Dict, Optional, Any, List


class PYG(object):
Copy link
Collaborator

Choose a reason for hiding this comment

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

I wonder about renaming the class. It kinda depends on what we expect this class to ultimately contain/do. Is it always going to be representative of a single borehole field? I'm open to suggestion, but this feels a bit vague.


class PYG(object):

def __init__(self, borehole_config_list: List[List[float]],
Copy link
Collaborator

Choose a reason for hiding this comment

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

I think it would be a bit nicer to pass in a list of structures instead of a list of list of floats. With the list of floats, our tools aren't able to help inform us of what each float should be. We could do some simple dataclasses that capture the different float members, and even add some helper functions to generate them for common cases. I just really want this API to be clean and self-documenting as much as possible.

options = {}

for borehole_config_items in borehole_config_list:
_borehole = Borehole(*borehole_config_items)
Copy link
Collaborator

Choose a reason for hiding this comment

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

And in the little structures, we could have a to_list method that smashes the values down to be consumed by PyGFunction. That keeps that implementation detail away from API consumers.

tests/test_api.py Outdated Show resolved Hide resolved

# -- Assert
assert isinstance(pyg_1.to_list(), list)
assert isinstance(pyg_2.to_list(), list)
Copy link
Collaborator

Choose a reason for hiding this comment

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

And before we get too much farther, we should probably grab a third-party source to generate some g-functions and do validation, or at least verification, on the g-function.

@mitchute
Copy link
Owner

@vtnate @Myoldmopar take a look. I still need to add some validation cases.

pygfunction/api.py Outdated Show resolved Hide resolved
tests/test_api.py Outdated Show resolved Hide resolved
tests/test_api.py Outdated Show resolved Hide resolved
tests/test_api.py Outdated Show resolved Hide resolved
Copy link
Collaborator

@Myoldmopar Myoldmopar left a comment

Choose a reason for hiding this comment

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

This looks quite good. Very usable, in my opinion.

self.tilt_angle_list: List[float] = [] # TODO: Is this degrees? And I assume from vertical?
self.orientation_angle_list: List[float] = [] # TODO: Same question
self.x_coords: List[float] = [] # TODO: What are the units here, I'm assuming meters
self.y_coords: List[float] = [] # TODO: Same question
Copy link
Collaborator

Choose a reason for hiding this comment

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

OK, modified this class just a bit. Now the constructor simply instantiates the lists. This will allow for us to create any variety of setup methods later on that can flexibly build out borehole fields, and the constructor won't need to change.

pygfunction/api.py Outdated Show resolved Hide resolved
def run_test(xy):
# compute g-functions
bh_field_params = BoreholeFieldParameters()
bh_field_params.initialize_borehole_field_generic(xy, self.height, self.depth, self.bh_radius)
Copy link
Collaborator

Choose a reason for hiding this comment

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

This is the real change, just moving the arguments out of the constructor to free us up to make changes later without breaking constructor calls.

Comment on lines 67 to 71
bh_field_params = BoreholeFieldParameters()
bh_field_params.initialize_borehole_field_generic(
xy_coords, self.height, self.depth, self.bh_radius, tilt_angle=20, orientation_angle=20
)
g = GFunctionGenerator(bh_field_params, self.alpha, self.time, solver_method="detailed")
Copy link
Owner

@mitchute mitchute Nov 12, 2024

Choose a reason for hiding this comment

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

@Myoldmopar should we just mash these into a single class? Surprisingly, I don't see a borehole field class in pygfunction. Maybe we just make this all a single basic class BoreholeField that does all this? It might be a bit more self-contained, and possibly a cleaner implementation.

bh_field = BoreholeField()
bh_field.init_generic_field(args...)
bh_field.generate_g_functions(args...)

Comment on lines 128 to 141
self.gfunc = gFunction(
self.as_config(),
alpha,
time=time,
boundary_condition=boundary_condition,
options=options,
method=solver_method,
)

return self.gfunc.gFunc

def to_list(self):
"""returns a _list_ of g-function values"""
return self.gfunc.gFunc.tolist()
Copy link
Owner

Choose a reason for hiding this comment

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

@Myoldmopar OK, I have combined the classes. Maybe one final (for now) question, is regarding the return types for each of these.

get_g_functions returns a numpy array; to_list converts that array to a list. Do these seem OK for now?

Copy link
Collaborator

Choose a reason for hiding this comment

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

If it were my personal preference alone, I would say we just have get_g_functions return the plain list of floats and leave numpy out of it. It's easy enough for an API client to create a numpy array of it if they want it. It may be a slight inconvenience to a few people, but it's going to be completely insignificant in runtime and keeps up operating on plain python as much as possible. And then we don't need this to_list function anymore.

If we don't want to go that far, then I might still be inclined to remove this as_list function and just return a numpy array from get_g_functions. The client can still do whatever they like with it.

Copy link
Owner

Choose a reason for hiding this comment

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

Make sense, hence my apprehension. I'll update that.

@@ -4,11 +4,12 @@
Setting up pygfunction
**********************

*pygfunction* uses Python 3.7, along with the following packages:
*pygfunction* uses Python 3.8, along with the following packages:
Copy link
Owner

Choose a reason for hiding this comment

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

@MassimoCimmino thanks, this looks great! One minor point - python 3.8 is past end of life, so perhaps in a separate PR this could be bumped up to a more current version.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Yes. I tend to update Python versions and dependencies on major and minor releases. This is one place where I had forgotten to update on the last version.

A little summary of my implementation if you want to discuss any of the changes:

  1. The gFunction class now accepts Borefield objects. That did not require changes in the gFunction class (outside of isinstance checks) since the new class behaves like a list of boreholes for all operations done in pygfunction.
  2. The Borefield.evaluate_g_function class method returns an array of g-function values. This was to align with the current gFunction.evaluate_g_function method.
  3. I moved borefield creation functions (e.g. boreholes.rectangle_field) to the Borefield class. They needed to be re-implemented to generate arrays of geometrical parameters instead of lists of boreholes. The functions in the boreholes module now have a deprecation warning.
  4. Examples are update to use the new interface for bore field creation.

Something to consider is if the boreholes.visualize_borefield should also be moved to the Borefield class. I remember we had a discussion about the dependency for matplotlib. An option is to include the method and import matplotlib at runtime within the method instead of the top of the module.

The PR is much larger than I expected. I will review it and make sure it is compatible with the planned changes for issue MassimoCimmino#210.

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

Successfully merging this pull request may close these issues.

4 participants