mbakker7 / timml

An analytic element model for steady multi-layer flow
MIT License
38 stars 21 forks source link

Refining elements into N segments #103

Open dbrakenhoff opened 12 months ago

dbrakenhoff commented 12 months ago

This is related to #89, but discusses a different style of refinement, subdividing lines into N segments. This is probably especially useful for Inhoms (e.g. BuildingPits).

The implementation requires some thought, hence this long and detailed post... This is just one idea, but maybe serves as a good starting point for figuring out the best way forward.

Refining elements into segments

The general idea is to give certain elements a Element._refine() method. This method is called in initialize() if refine=True for that particular element. The _refine() method takes n as an argument, which splits line segments into $N$ segments, and returns a list of new smaller (shorter?) elements.

Some requirements:

Limitations:

Some example code:

ml = tml.ModelMaq(...)
bpit = tml.LeakyBuildingPitMaq(..., refine=True)
well = tml.Well(...)
ml.solve()

ml.elements  # contains Well
ml.aq.inhoms  # contains original LeakyBuildingPitMaq

ml.aq.inhomlist  # contains refined LeakyBuildingPitMaq
ml.elementlist  # all computation elements, so including refined intlinesinks created by inhom

Distinguishing between user-added elements and internally added elements

For this to work, there needs to be a distinction between elements specified by the user and elements that are created internally when refine is called.

All elements and inhoms will need an addtomodel option, to prevent internally created elements from getting added to the user element list. Some elements already have this, but now more will need this option.

The different types of elements

Workflow

For elements

  1. Model.add_element() adds element to Model.elements
    • For simple elements this is a straight-forward add
    • For compound elements, the compound element is added, not its sub-elements.
    • This list tracks all user-added elements.
  2. Model.initialize()
    • Model.elementlist is populated in this step, for collecting all computation elements.
    • Optionally calls refine. Refine creates new elements (which are not added to the model in constructor, addtomodel=False) that are added to Model.elementlist for the computation.
    • Adds original or refined elements to model.aq.elementlist.
  3. Model.elementlist and Model.aq.elementlist start empty at the start of every solve.

For inhoms

  1. Model.aq.add_inhom() adds inhom to the user-inhom list Model.aq.inhoms.
    • This provides a list of inhoms added by the user.
  2. Model.aq.initialize() eventually calls inhom.create_elements()
    • create_elements creates elements that are added to the computation list Model.elementlist. These elements are added manually, and not by the constructors of the elements themselves (addtomodel=False).
    • Optionally calls refine. Refine creates a new refined inhom (this refined inhom is not added to model.aq.inhoms, but added to computation inhom list: model.aq.inhomlist).
    • Calls create_elements on refined or original Inhom.

Refinement based on proximity vertices

Link to issue #89.

Additional automatic refinement is desirable when elements lie close to one another, e.g. a well near an impermeable wall. The modifications presented here should also make it possible to implement this style of refinement.

One challenge is that the current segment-splitting refinement will modify the vertices of elements in the computation. These refined elements only become available in the initialize function, so not all vertices may be known yet in the initialize phase. That means a second loop through the element list is required in Model.initialize() to further refine them based on vertices.

# in Model.initialize()

# 1. optionally refine elements and build computation element list

# 2. optionally refine and initialize inhoms, adding inhom elements to computation list

# collect vertices from original elements.
refined_element_list = [] # new list for further refinement
xverts = np.concatenate([e.x for e in self.elements])
yverts = np.concatenate([e.y for e in self.elements])
for e in self.elementlist:
   if hasattr(e, "_refine_vertices"):
      refined_elems = e._refine_vertices(xverts, yverts)
      refined_element_list += refined_elems
   else:
      refined_element_list.append(e)
# overwrite original elementlist
self.elementlist = refined_element_list
for e in self.elementlist:
   e.initialize()

Questions

dbrakenhoff commented 11 months ago