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

add isochrone script #896

Merged
merged 4 commits into from Oct 27, 2023
Merged

add isochrone script #896

merged 4 commits into from Oct 27, 2023

Conversation

NicoSchlw
Copy link
Contributor

This script computes the isochrones (on-fault contours that are associated with acceleration waveforms) of a certain surface receiver.

This script computes the isochrones (on-fault contours that are associated with acceleration waveforms) of a certain surface receiver.
@krenzland
Copy link
Contributor

I would recommend that we follow the standard Python naming/style conventions: https://peps.python.org/pep-0008/?

@Thomas-Ulrich
Copy link
Contributor

I suppose that means applying a linter (e.g. black but you probably already did that) and checking style errors detected by flake8?

@krenzland
Copy link
Contributor

The most obvious issue is the naming scheme: In Python, functions always use snake_case instead of CamelCase.

Copy link
Contributor

@Thomas-Ulrich Thomas-Ulrich left a comment

Choose a reason for hiding this comment

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

here is my first round of comments.
(I may have more comments if I find time to test it).

help='provide path+filename-fault.xdmf; only needed when the path differs from the -surface.xdmf file')

parser.add_argument('--output', choices=["numpy","xdmf","both"], default="xdmf",
help='choose the output format')
Copy link
Contributor

Choose a reason for hiding this comment

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

For consistency with the rest: help='output format'

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

parser.add_argument('--output', choices=["numpy","xdmf","both"], default="xdmf",
help='choose the output format')

parser.add_argument('--sliprateThreshold', nargs=1, default=([0.05]), metavar=('peak sliprate threshold in m/s'),
Copy link
Contributor

Choose a reason for hiding this comment

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

slip-rate

Copy link
Contributor

Choose a reason for hiding this comment

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

--slipRateThreshold ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

help='choose the output format')

parser.add_argument('--sliprateThreshold', nargs=1, default=([0.05]), metavar=('peak sliprate threshold in m/s'),
help='fault cells below the treshold are assumed to not radiate waves' , type=float)
Copy link
Contributor

Choose a reason for hiding this comment

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

threshold

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

parser.add_argument('--sliprateThreshold', nargs=1, default=([0.05]), metavar=('peak sliprate threshold in m/s'),
help='fault cells below the treshold are assumed to not radiate waves' , type=float)

parser.add_argument('--sliprateParameters', choices=["absolute","components","both"], default="both",
Copy link
Contributor

Choose a reason for hiding this comment

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

--slipRateParameters?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

help="absolute: compute isochrones from peak times of absolute slip rates;"+
" components: peak slip rate times are computed for SRs and SRd separately")

parser.add_argument('--medianFilterRadius', nargs=1, default=([0.]), metavar=('radius of the median filter in m'),
Copy link
Contributor

Choose a reason for hiding this comment

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

maybe remove the default, and add required=True, forcing the user to have this (maybe) important decision?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, good idea


def ApproximateHypocenter(absoluteSliprates, faultCells, slipRateThreshold=args.sliprateThreshold[0]):

slippingFault = np.where(absoluteSliprates > slipRateThreshold, 1, 0)
Copy link
Contributor

Choose a reason for hiding this comment

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

wouldn't it be more simple to use the min of RT variable?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, I rewrote the function

faultXdmf = seissolxdmf.seissolxdmf(args.faultXdmf)
faultGeom = faultXdmf.ReadGeometry()
faultConnect = faultXdmf.ReadConnect()
faultCells = ComputeTriangleMidpoints(faultGeom, faultConnect)
Copy link
Contributor

Choose a reason for hiding this comment

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

faultMidPoints = ...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

print(f"sliprates.shape: {sliprates.shape}")

stop1 = timeit.default_timer()
print(f"Time to load data: {np.round(stop1 - start, 2)}")
Copy link
Contributor

Choose a reason for hiding this comment

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

print(f"Time to load data: {stop1 - start:.2f}"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

print(f"Hypocenter approximated at: {np.round(hypocenter)}")

avgSwaveVelocity = np.linalg.norm(receiverCoords - hypocenter) / receiverPwaveTraveltime / np.sqrt(3) # Assumes a Poisson solid
print(f"Average S-wave velocity between fault and receiver: {np.round(avgSwaveVelocity,2)}")
Copy link
Contributor

Choose a reason for hiding this comment

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

between hypocenter and receiver

Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes

stop3 = timeit.default_timer()
print("Saving output...")

prefix = f"isochrones_{np.int(receiverCoords[0])}_{np.int(receiverCoords[1])}_{np.int(receiverCoords[2])}"
Copy link
Contributor

Choose a reason for hiding this comment

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

f"isochrones_{receiverCoords[0]:.0f}_(...)"

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

Copy link
Contributor Author

@NicoSchlw NicoSchlw left a comment

Choose a reason for hiding this comment

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

Thanks for your feedback!

help='provide path+filename-fault.xdmf; only needed when the path differs from the -surface.xdmf file')

parser.add_argument('--output', choices=["numpy","xdmf","both"], default="xdmf",
help='choose the output format')
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

parser.add_argument('--output', choices=["numpy","xdmf","both"], default="xdmf",
help='choose the output format')

parser.add_argument('--sliprateThreshold', nargs=1, default=([0.05]), metavar=('peak sliprate threshold in m/s'),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

help='choose the output format')

parser.add_argument('--sliprateThreshold', nargs=1, default=([0.05]), metavar=('peak sliprate threshold in m/s'),
help='fault cells below the treshold are assumed to not radiate waves' , type=float)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

parser.add_argument('--sliprateThreshold', nargs=1, default=([0.05]), metavar=('peak sliprate threshold in m/s'),
help='fault cells below the treshold are assumed to not radiate waves' , type=float)

parser.add_argument('--sliprateParameters', choices=["absolute","components","both"], default="both",
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

help="absolute: compute isochrones from peak times of absolute slip rates;"+
" components: peak slip rate times are computed for SRs and SRd separately")

parser.add_argument('--medianFilterRadius', nargs=1, default=([0.]), metavar=('radius of the median filter in m'),
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes, good idea

faultXdmf = seissolxdmf.seissolxdmf(args.faultXdmf)
faultGeom = faultXdmf.ReadGeometry()
faultConnect = faultXdmf.ReadConnect()
faultCells = ComputeTriangleMidpoints(faultGeom, faultConnect)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

ruptureTimes = faultXdmf.ReadData("RT", timeIndicesFault[1]-1)

if args.events[0] == 2:
ruptureTimes = ruptureTimes - faultXdmf.ReadData("RT", timeIndicesFault[0]-1)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

this sets all RTs to zero that slipped before timeIndicesFault[0]

print(f"sliprates.shape: {sliprates.shape}")

stop1 = timeit.default_timer()
print(f"Time to load data: {np.round(stop1 - start, 2)}")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

print(f"Hypocenter approximated at: {np.round(hypocenter)}")

avgSwaveVelocity = np.linalg.norm(receiverCoords - hypocenter) / receiverPwaveTraveltime / np.sqrt(3) # Assumes a Poisson solid
print(f"Average S-wave velocity between fault and receiver: {np.round(avgSwaveVelocity,2)}")
Copy link
Contributor Author

Choose a reason for hiding this comment

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

yes

stop3 = timeit.default_timer()
print("Saving output...")

prefix = f"isochrones_{np.int(receiverCoords[0])}_{np.int(receiverCoords[1])}_{np.int(receiverCoords[2])}"
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

@NicoSchlw
Copy link
Contributor Author

@krenzland I will follow the style guide in the future. Do you think it's necessary to update the function/variable names of this script?


def ComputeTriangleMidpoints(geom, connect):
"""Generates an array with coordinates of triangle midpoints (same dimension as connect)"""
xyz = np.zeros_like(connect, dtype=float)
Copy link
Contributor

Choose a reason for hiding this comment

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

this line is not required

Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed



def LoadSliprates(sliprateParameters):
sliprates = []
Copy link
Contributor

Choose a reason for hiding this comment

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

sliprates = {}
sliprates["SRs"] = tXdmf.ReadData("SRs")[timeIndicesFault[0]:timeIndicesFault[1]].T
sliprates["SRd"] = tXdmf.ReadData("SRd")[timeIndicesFault[0]:timeIndicesFault[1]].T
etc.
I mean it would be a dictionary, with keys SRs, SRd, and ASR pointing to numpy array.

faultConnect = faultXdmf.ReadConnect()
faultMidPoints = ComputeTriangleMidpoints(faultGeom, faultConnect)
dtFault = faultXdmf.ReadTimeStep()
timeIndicesFault = np.array(args.timeStepRange)
Copy link
Contributor

Choose a reason for hiding this comment

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

What about naming this array faultSlicingRange? indexBoundsFault?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed


print("Loading data...")
surfaceXdmf = seissolxdmf.seissolxdmf(args.filename)
surfaceCells = ComputeTriangleMidpoints(surfaceXdmf.ReadGeometry(), surfaceXdmf.ReadConnect())
Copy link
Contributor

Choose a reason for hiding this comment

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

rename to surfaceCellBarycenters? or freeSurfaceBarycenters?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed

def PTraveltime(receiverIndex, trigger=0.01):
"""Picks the P-wave arrival. This function is only suited for synthetic data with negligible noise"""

timeIndices = np.int_(timeIndicesFault * (dtFault / dtSurface))
Copy link
Contributor

Choose a reason for hiding this comment

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

freeSurfaceSlicingRange?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed

surfaceXdmf.ReadDataChunk(surfaceVariables[2], receiverIndex, 1)**2)[timeIndices[0]:timeIndices[1]]

threshold = np.amax(absWaveform)*trigger # trigger dictates the portion of the maximum ground velocity,
t = np.argmax(absWaveform>threshold)*dtSurface # which needs to be reached to pick the p-phase
Copy link
Contributor

Choose a reason for hiding this comment

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

return np.argmax(absWaveform>threshold)*dtSurface

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

stop2 = timeit.default_timer()
print(f"Time to compute values: {stop2 - stop1:.2f}")

if args.medianFilterRadius[0] > 0.:
Copy link
Contributor

Choose a reason for hiding this comment

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

or
if args.medianFilterRadius:

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


def MedianSmoothingParallel(parameter, xyz, radius=250., nprocs=4):

chunks = [tuple((parameter, xyz, i, radius)) for i in np.array_split(np.arange(parameter.shape[0]), nprocs)]
Copy link
Contributor

Choose a reason for hiding this comment

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

chunks = [(parameter, xyz, i, radius) for i in np.array_split(np.arange(parameter.shape[0]), nprocs)]
should be sufficient.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

return parameter_new


def MedianSmoothingParallel(parameter, xyz, radius=250., nprocs=4):
Copy link
Contributor

Choose a reason for hiding this comment

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

would be clear if parameter was called
peakSliprateTimes (or a slightly different name if you want to avoid the name of the global value)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed

parameter = input_tuple[0]
xyz = input_tuple[1]
indices = input_tuple[2]
radius = input_tuple[3]
Copy link
Contributor

Choose a reason for hiding this comment

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

parameter, xyz, indices, radius = input_tuple

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

radius = input_tuple[3]
parameter_new = np.zeros(indices.shape[0])
for i, j in enumerate(indices):
parameter_new[i] = np.median(parameter[(xyz[:,0] < xyz[j,0]+radius) & (xyz[:,0] > xyz[j,0]-radius) &
Copy link
Contributor

Choose a reason for hiding this comment

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

for i, j in enumerate(indices):
  xyz0=  xyz[j,:]
  distances = np.linalg.norm(xyz - xyz0, axis=1)
  indices_within_sphere = np.where(distances <= radius)[0]
  parameter_new[i] =np.median(parameters[indices_within_sphere])

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, much cleaner

Copy link
Contributor Author

@NicoSchlw NicoSchlw left a comment

Choose a reason for hiding this comment

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

Thanks for your review!



def LoadSliprates(sliprateParameters):
sliprates = []
Copy link
Contributor Author

Choose a reason for hiding this comment

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

But later array operations are performed on sliprates. If it was a dictionary, there would be a loop necessary each time.
E.g., here:

sliprates = np.where(sliprates >= args.slipRateThreshold[0], sliprates, 0.)  # Set slip rates below the threshold to zero
peakSliprateTimes = np.argmax(sliprates, axis=2) * dtFault

def PTraveltime(receiverIndex, trigger=0.01):
"""Picks the P-wave arrival. This function is only suited for synthetic data with negligible noise"""

timeIndices = np.int_(timeIndicesFault * (dtFault / dtSurface))
Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed

faultConnect = faultXdmf.ReadConnect()
faultMidPoints = ComputeTriangleMidpoints(faultGeom, faultConnect)
dtFault = faultXdmf.ReadTimeStep()
timeIndicesFault = np.array(args.timeStepRange)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed

stop2 = timeit.default_timer()
print(f"Time to compute values: {stop2 - stop1:.2f}")

if args.medianFilterRadius[0] > 0.:
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


def ComputeTriangleMidpoints(geom, connect):
"""Generates an array with coordinates of triangle midpoints (same dimension as connect)"""
xyz = np.zeros_like(connect, dtype=float)
Copy link
Contributor Author

Choose a reason for hiding this comment

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

removed

parameter = input_tuple[0]
xyz = input_tuple[1]
indices = input_tuple[2]
radius = input_tuple[3]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

radius = input_tuple[3]
parameter_new = np.zeros(indices.shape[0])
for i, j in enumerate(indices):
parameter_new[i] = np.median(parameter[(xyz[:,0] < xyz[j,0]+radius) & (xyz[:,0] > xyz[j,0]-radius) &
Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, much cleaner

return parameter_new


def MedianSmoothingParallel(parameter, xyz, radius=250., nprocs=4):
Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed


def MedianSmoothingParallel(parameter, xyz, radius=250., nprocs=4):

chunks = [tuple((parameter, xyz, i, radius)) for i in np.array_split(np.arange(parameter.shape[0]), nprocs)]
Copy link
Contributor Author

Choose a reason for hiding this comment

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

done


print("Loading data...")
surfaceXdmf = seissolxdmf.seissolxdmf(args.filename)
surfaceCells = ComputeTriangleMidpoints(surfaceXdmf.ReadGeometry(), surfaceXdmf.ReadConnect())
Copy link
Contributor Author

Choose a reason for hiding this comment

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

renamed

@codecov-commenter
Copy link

codecov-commenter commented Oct 23, 2023

Codecov Report

Merging #896 (ffb882e) into master (f0d0fd3) will not change coverage.
The diff coverage is n/a.

❗ Your organization needs to install the Codecov GitHub app to enable full functionality.

@@           Coverage Diff           @@
##           master     #896   +/-   ##
=======================================
  Coverage   14.35%   14.35%           
=======================================
  Files         253      253           
  Lines       14253    14253           
=======================================
  Hits         2046     2046           
  Misses      12207    12207           

📣 We’re building smart automated test selection to slash your CI/CD build times. Learn more

stop2 = timeit.default_timer()
print(f"Time to compute values: {stop2 - stop1:.2f}")

if args.medianFilterRadius[0]:
Copy link
Contributor

Choose a reason for hiding this comment

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

does this really works?
I would have think only if args.medianFilterRadius: works.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yes, it works because it's a required parameter

Copy link
Contributor

Choose a reason for hiding this comment

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

then it is always true.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No, you deactivate it by setting it to zero

@NicoSchlw NicoSchlw added this pull request to the merge queue Oct 27, 2023
Merged via the queue into master with commit 274d643 Oct 27, 2023
5 checks passed
@davschneller davschneller deleted the NicoSchlw-patch-2 branch October 30, 2023 09:46
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.

None yet

4 participants