# ------------------------------------------------------------------------------------------------------------
# SPDX-License-Identifier: LGPL-2.1-only
#
# Copyright (c) 2016-2024 Lawrence Livermore National Security LLC
# Copyright (c) 2018-2024 TotalEnergies
# Copyright (c) 2018-2024 The Board of Trustees of the Leland Stanford Junior University
# Copyright (c) 2023-2024 Chevron
# Copyright (c) 2019- GEOS/GEOSX Contributors
# Copyright (c) 2019- INRIA project-team Makutu
# All rights reserved
#
# See top level LICENSE, COPYRIGHT, CONTRIBUTORS, NOTICE, and ACKNOWLEDGEMENTS files for details.
# ------------------------------------------------------------------------------------------------------------
import numpy as np
from typing import Iterable, List, Optional, Union
from typing_extensions import Self
from geos.pygeos_tools.input.Xml import XML
Coordinates3D = Iterable[ float ]
[docs]
class Shot:
"""Class representing a shot configuration : a SourceSet of 1 Source and a ReceiverSet
Attributes
----------
source :
Source object
receivers :
ReceiverSet object
flag :
A flag to say if the shot configuration has been simulated
"Undone", "In Progress", "Done"
id : str
Number identification
mesh : Mesh object
Mesh of the problem
xml : XML
xml input file of the shot
"""
def __init__( self: Self, sourceSet=None, receiverSet=None, shotId: str = None ):
""" Constructor of Shot
Parameters
----------
sourceSet : SourceSet, optional
Set of Sources \
receiverSet : ReceiverSet, optional
Set of receivers
shotId : str
Number identification
"""
if sourceSet is None:
sourceSet = SourceSet()
else:
assert isinstance( sourceSet, SourceSet ), "SourceSet instance expected for `sourceSet` argument"
self.sources = sourceSet
if receiverSet is None:
receiverSet = ReceiverSet()
else:
assert isinstance( receiverSet, ReceiverSet ), "ReceiverSet instance expected for `receiverSet` argument"
self.receivers = receiverSet
self.flag = "Undone"
self.dt = None
self.id = shotId
self.mesh = None
def __eq__( self: Self, other ):
if isinstance( self, other.__class__ ):
if self.sources == other.sources and self.receivers == other.receivers:
return True
return False
def __repr__( self: Self ):
return 'Source position : \n' + str( self.sources ) + ' \n\n' + 'Receivers positions : \n' + str(
self.receivers ) + '\n\n'
"""
Accessors
"""
[docs]
def getMesh( self: Self ):
"""Get the mesh"""
return self.mesh
[docs]
def getSourceList( self: Self ) -> List:
"""
Return the list of all sources in the Shot configuration
Returns
--------
list of Source
list of all the sources
"""
return self.sources.getList()
[docs]
def getSourceCoords( self: Self ) -> List[ Coordinates3D ]:
"""
Return the list of all sources coordinates in the Shot configuration
Returns
--------
list of list
list of all the sources coordinates
"""
return self.sources.getSourceCoords()
[docs]
def getReceiverCoords( self: Self ) -> List[ Coordinates3D ]:
"""
Return the list of all receivers coordinates in the Shot configuration
Returns
--------
list of list
list of all the receivers coordinates
"""
return self.receivers.getReceiverCoords()
[docs]
def getReceiverList( self: Self ) -> List:
"""
Return the list of all receivers in the Shot configuration
Returns
--------
list of Receiver
list of all the sources
"""
return self.receivers.getList()
"""
Mutators
"""
[docs]
def setXml( self: Self, xml: XML ):
"""
Set the Xml for the shot
Parameters
----------
xml : XML
XML object corresponding to the GEOS xml input file
"""
self.xml: XML = xml
[docs]
def setMesh( self: Self, mesh ):
"""
Set the mesh
Parameters
-----------
mesh : Mesh
Mesh of the shot
"""
self.mesh = mesh
[docs]
def loadMesh( self: Self ):
"""Load the mesh and set its properties"""
if self.mesh and not self.mesh.isSet:
self.mesh.updateMeshProperties()
[docs]
class ShotPoint:
"""
Class defining the methods common to shot points (Source or Receiver)
Attributes
-----------
coords : list of float
Coordinates of the shot point
"""
def __init__( self: Self, x, y, z ):
"""
Parameters
-----------
x : str, int or float
x coordinate
y : str, int or float
y coordinate
z : str, int or float
z coordinate
"""
self.setPosition( x, y, z ) # defines the self.coords attribute
def __str__( self: Self ):
return f'Position of Shot point : {self.coords}'
def __repr__( self: Self ):
return f'ShotPoint({self.coords[ 0 ]}, {self.coords[ 1 ]}, {self.coords[ 2 ]})'
def __eq__( self: Self, other ):
if isinstance( self, other.__class__ ):
if self.coords == other.coords:
return True
return False
"""
Accessors
"""
[docs]
def getPosition( self: Self ) -> List:
"""
Return the position coordinates
Returns
-----------
list
Coordinates
"""
return self.coords
@property
def x( self: Self ) -> float:
"""
Get the x position
Returns
--------
float
X coordinate
"""
return self.coords[ 0 ]
@property
def y( self: Self ) -> float:
"""
Get the y position
Returns
--------
float
Y coordinate
"""
return self.coords[ 1 ]
@property
def z( self: Self ) -> float:
"""
Get the z position
Returns
--------
float
Z coordinate
"""
return self.coords[ 2 ]
"""
Mutators
"""
[docs]
def setCoordinate( self: Self, coord: int, value: Union[ int, float ] ) -> None:
"""
Set one of the coordinates
Parameters
-----------
coord : int
Which coordinate to update \
Choices are 0, 1, 2
value : float or int
New value
"""
assert coord in ( 0, 1, 2 ), "coord can only be 0, 1 or 2"
assert isinstance( value, float ) or isinstance( value, int )
self.coords[ coord ] = value
[docs]
def setPosition( self: Self, x, y, z ) -> None:
"""
Set all the coordinates
Parameters
-----------
coords : list or array of len 3
New coordinates
"""
assert all(
str( c ).replace( ".", "", 1 ).isdigit() or isinstance( c, float ) or isinstance( c, int )
for c in ( x, y, z ) ), "Only numeric values are accepted"
self.coords: Coordinates3D = [ float( c ) for c in ( x, y, z ) ]
[docs]
def isinBounds( self: Self, b ) -> bool:
"""
Check if the receiver is in the bounds
Parameters
-----------
b : list or array of len 6
Bounds of format \
(xmin, xmax, ymin, ymax, zmin, zmax)
Returns
--------
bool
True if receiver is in bounds, False otherwise
"""
return ( b[ 0 ] <= self.x() <= b[ 1 ] and b[ 2 ] <= self.y() <= b[ 3 ] and b[ 4 ] <= self.z() <= b[ 5 ] )
[docs]
class Receiver( ShotPoint ):
"""A class representing a receiver
Attributes
----------
coords :
Coordinates of the receiver
"""
def __init__( self: Self, x, y, z ):
"""Constructor for the receiver
Parameters
----------
pos : len 3 array-like
Coordinates for the receiver
"""
super().__init__( x, y, z )
def __str__( self: Self ):
return f'Position of Receiver : {self.coords}'
def __repr__( self: Self ):
return f'Receiver({self.coords[ 0 ]}, {self.coords[ 1 ]}, {self.coords[ 2 ]})'
[docs]
class Source( ShotPoint ):
"""A class representing a point source
Attributes
----------
coords : list of float
Coordinates of the source
"""
def __init__( self: Self, x, y, z ):
"""Constructor for the point source
Parameters
----------
coords : list of float
Coordinates for the point source
"""
super().__init__( x, y, z )
def __str__( self: Self ):
return f'Position of Source : {self.coords}'
def __repr__( self: Self ):
return f'Source({self.coords[ 0 ]}, {self.coords[ 1 ]}, {self.coords[ 2 ]})'
[docs]
class ShotPointSet:
"""
Class defining methods for sets of shot points
Attributes
-----------
list : list
List of ShotPoint
number : int
Number of ShotPoint in the set
"""
def __init__( self: Self, shotPointList: List[ ShotPoint ] = None ):
"""
Parameters
-----------
shotPointList : list
List of ShotPoint \
Default is None
"""
self.updateList( shotPointList ) # defines the self.list and self.number attributes
def __eq__( self: Self, other ):
if isinstance( self, other.__class__ ):
if self.number == other.number:
for sp1 in self.list:
if self.list.count( sp1 ) != other.list.count( sp1 ):
return False
return True
return False
[docs]
def getList( self: Self ) -> List[ ShotPoint ]:
"""
Return the list of Shot points in the set
Returns
--------
list
List of Shot Points
"""
return self.list
[docs]
def updateList( self: Self, newList: List[ ShotPoint ] = None ) -> None:
"""
Update the full list with a new one
Parameters
-----------
newList : list
New shot points set \
Default is empty list (reset)
"""
if newList is None:
self.list = list()
else:
assert ( isinstance( newList, list ) or isinstance( newList, tuple ) )
assert all( isinstance( sp, ShotPoint )
for sp in newList ), "`shotPointList` should only contain `ShotPoint` instances"
self.list = list( newList )
self.number: int = len( self.list )
[docs]
def append( self: Self, shotPoint: ShotPoint = None ) -> None:
"""
Append a new shot point to the set
Parameters
-----------
shotPoint : ShotPoint
Element to be added
"""
if shotPoint is not None:
assert isinstance( shotPoint, ShotPoint ), "Can only add a `ShotPoint` object to the set"
self.list.append( shotPoint )
self.number += 1
[docs]
def appendSet( self: Self, shotPointSet ) -> None:
"""
Append a list or a set of Shot Points to the existing one
Parameters
-----------
shotPointSet : list or ShotPointSet
Set of shot points to be added
"""
if isinstance( shotPointSet, list ):
shotPointList = shotPointSet
elif isinstance( shotPointSet, ShotPointSet ):
shotPointList = shotPointSet.getList()
else:
raise TypeError( "Only Sets and list objects are acceptable" )
for shotPoint in shotPointList:
self.append( shotPoint )
[docs]
class ReceiverSet( ShotPointSet ):
"""
Class representing a set receiver
Attributes
----------
list : list
List of Receivers
number : int
Number of Receivers
"""
def __init__( self: Self, receiverList: List[ Receiver ] = None ):
"""Constructor for the receiver set
Parameters
----------
receiverList : list of Receiver
List of Receiver
"""
super().__init__( receiverList )
def __repr__( self: Self ):
if self.number >= 10:
return str( self.list[ 0:4 ] )[ :-1 ] + '...' + '\n' + str( self.list[ -4: ] )[ 1: ]
else:
return str( self.list )
"""
Accessors
"""
[docs]
def getReceiver( self: Self, i ) -> int:
"""
Get a specific receiver from the set with its index
Parameters
-----------
i : int
Index of the receiver requested
"""
if len( self.list ) - 1 >= i:
return self.list[ i ]
else:
raise IndexError( "The receiver set is smaller than the index requested" )
[docs]
def getReceiverCoords( self: Self ) -> List[ Coordinates3D ]:
"""
Get the coordinates of all the receivers
Returns
--------
receiverCoords : list of Coordinates3D
List of all the receivers positions
"""
return [ receiver.coords for receiver in self.getList() ]
[docs]
def keepReceiversWithinBounds( self: Self, bounds ) -> None:
"""
Filter the list to keep only the ones in the given bounds
Parameters
-----------
bounds : list or array of len 6
Bounds of format \
(xmin, xmax, ymin, ymax, zmin, zmax)
"""
newList: List = list()
for receiver in self.list:
if receiver.isinBounds( bounds ):
newList.append( receiver )
self.updateList( newList )
[docs]
def append( self: Self, receiver ) -> None:
"""
Append a new receiver to the receiver set
Parameters
----------
receiver : Receiver
Receiver to be added
"""
assert isinstance( receiver, Receiver )
super().append( receiver )
[docs]
class SourceSet( ShotPointSet ):
"""
Class representing a source set
Attributes
----------
list :
List of sources
number :
Number of sources
"""
def __init__( self: Self, sourceList=None ):
"""Constructor for the source set
Parameters
----------
sourceList : list of Source
List of sources
"""
super().__init__( sourceList )
def __repr__( self: Self ):
if self.number >= 10:
return str( self.list[ 0:4 ] )[ :-1 ] + '...' + '\n' + str( self.list[ -4: ] )[ 1: ]
else:
return str( self.list )
"""
Accessors
"""
[docs]
def getCenter( self: Self ) -> Optional[ Coordinates3D ]:
"""
Get the position of the center of the SourceSet
Returns
--------
center : tuple or None
Central position of the source set
"""
if self.number > 0:
return tuple( np.mean( np.array( self.getSourceCoords() ), axis=0 ) )
[docs]
def getSource( self: Self, i ) -> int:
"""
Get a specific source from the set with its index
Parameters
-----------
i : int
Index of the source requested
"""
if len( self.list ) - 1 >= i:
return self.list[ i ]
else:
raise IndexError( "The source set is smaller than the index requested" )
[docs]
def getSourceCoords( self: Self ) -> List[ Coordinates3D ]:
"""
Get the coordinates of all the sources
Returns
--------
sourceCoords : list of Coordinates3D
List of all the source positions
"""
return [ source.coords for source in self.getList() ]
[docs]
def append( self: Self, source ) -> None:
"""
Append a new source to the source set
Parameters
----------
source : Source
Source to be added
"""
assert isinstance( source, Source )
super().append( source )