Commit 118f6a28 authored by Edward Fauchon-Jones's avatar Edward Fauchon-Jones
Browse files

Merge branch 'mass-ordering-check' into 'master'

Mass ordering check

Closes #4 and #8

See merge request waveforms/lvcnrpy!8
parents d985b525 6e235270
......@@ -20,7 +20,9 @@ import numpy as np
from lvcnrpy.Sim import Sim
from colorama import Fore, Back, Style
import h5py as h5
from lvcnrpy.format.format import format1, format2, format3
from lvcnrpy.format.format import format1, format1Interfield
from lvcnrpy.format.format import format2, format2Interfield
from lvcnrpy.format.format import format3
import lvcnrpy.format.specs as specs
import lvcnrpy.format.errors as err
import sys
......@@ -44,14 +46,19 @@ args = parser.parse_args()
# Define output syntax for a single field report
FIELD = u'- [{0:s}{{0:s}}{1:s}] {{1:s}} ({{2:}}) {{3:}}'
INTERFIELD = u'- [{0:s}{{0:s}}{1:s}] {{1:s}} {{2:}}'
if args.col:
# Define possible field output templates
# Define possible field output templates without color
FIELD_VALID = FIELD.format('', '')
FIELD_INVALID = FIELD.format('', '')
def checkField(sim, field):
......@@ -81,6 +88,22 @@ def checkField(sim, field):
print FIELD_INVALID.format(,, value, msg).strip()
return 1
def checkInterfield(sim, interfield):
# Check validity of interfield against specification class `interfield`
valid = interfield.valid(sim)
# Condition on its type of validity
if isinstance(valid, err.ValidInterfield):
print INTERFIELD_VALID.format(,, valid.msg).strip()
return 0
print INTERFIELD_INVALID.format(,, valid.msg).strip()
return 1
if __name__ == '__main__':
# Initialise checkInt. checkInt = 0 for pass and >0 for fail.
......@@ -99,6 +122,14 @@ if __name__ == '__main__':
# Format 1 interfield checks
print('# Format 1 (Interfield)\n')
for interfieldKey, interfield in format1Interfield.items():
checkInt += checkInterfield(sim, interfield())
# Determine format level to check
fmt = args.format or sim.att('Format') or 1
......@@ -111,6 +142,14 @@ if __name__ == '__main__':
# Format 2 interfield checks
print('# Format 2 (Interfield)\n')
for interfieldKey, interfield in format2Interfield.items():
checkInt += checkInterfield(sim, interfield())
if fmt > 2:
# Format 3
print('# Format 3\n')
......@@ -44,6 +44,15 @@ class Valid(Error):
name = "="
class ValidInterfield(Error):
"""No error for interfield"""
name = "="
msg = "({0:s})"
def __init__(self, spec):
self.msg = self.msg.format(spec.validMsg)
class InvalidValue(Error):
"""Defined value is not a value the field may take"""
......@@ -96,3 +105,27 @@ class InvalidModeName(Error):
def __init__(self, spec):
self.msg = self.msg.format(spec.m, spec.l)
class InvalidInterfield(Error):
"""Interfield check has failed"""
msg = "({0:s})"
def __init__(self, spec):
self.msg = self.msg.format(spec.invalidMsg)
class InvalidInterfields(Error):
"""Interfield check failed because the field dependencies are invalid"""
msg = "(Field dependencies are invalid)"
class InvalidSequence(Error):
"""Defined sequence is not valid"""
msg = "({0:s})"
def __init__(self, spec):
self.msg = self.msg.format(spec.invalidMsg)
......@@ -60,6 +60,9 @@ format1 = OrderedDict((
("eccentricity", Eccentricity),
("mean_anomaly", MeanAnomaly))))))
format1Interfield = OrderedDict((
("mass-ordering", MassOrdering),))
format2 = OrderedDict((
('mass1-vs-time', Mass1VsTime),
('mass2-vs-time', Mass2VsTime),
......@@ -80,6 +83,9 @@ format2 = OrderedDict((
('LNhatz-vs-time', LNhatzVsTime),
('Omega-vs-time', OmegaVsTime)))
format2Interfield = OrderedDict((
('mass-vs-time-ordering', MassVsTimeOrdering),))
format3 = OrderedDict((
('remnant-mass-vs-time', RemnantMassVsTime),
('remnant-spinx-vs-time', RemnantSpinxVsTime),
......@@ -170,6 +170,70 @@ class ROMSplineSpec(Spec):
group.create_dataset(sub, data=data)
class InterfieldSpec(Spec):
"""Specification for relationship between mutliple fields"""
dtype = basestring
validMsg = "(Relationship betwen fields is valid)"
invalidMsg = "(Relationship betwen fields is invalid)"
def valid(self, sim):
return err.ValidInterfield(self)
def createField(self, sim):
def isclose(a, b, rel_tol=1e-09, abs_tol=0.0):
"""A Function for testing approximate equality
For backward compitibilty when not running with Python >= 3.5.
a, b: float
Values to comapre.
rel_tol: float
The relative tolerance - it is the maximum allowed difference between a
and b, relative to the larger absolute value of a or b. For example, to
set a tolerance of 5%, pass rel_tol=0.05. The default tolerance is
1e-09, which assures that the two values are the same within about 9
decimal digits. rel_tol must be greater than zero.
abs_tol: float
The minimum absolute tolerance - useful for comparisons near zero.
abs_tol must be at least zero.
isclose: bool
Return True if the values a and b are close to each other and False
return abs(a-b) <= max(rel_tol * max(abs(a), abs(b)), abs_tol)
def ismonotonic(x):
"""A Function for testing is a list/array is monotonic
a: list or numpy array of values to test.
isclose: bool
Return True if the list x is monotonic and False otherwise.
dx = np.diff(x)
return np.all(dx <= 0) or np.all(dx >= 0)
# General Fields
class Type(Spec):
"""Specification for the `type` field"""
......@@ -299,18 +363,27 @@ class Mass1(Spec):
name = "mass1"
dtype = float
def createField(self, sim):
sim.attrs[] = 4.0
class Mass2(Spec):
"""Specification for the `mass2` field"""
name = "mass2"
dtype = float
def createField(self, sim):
sim.attrs[] = 1.0
class Eta(Spec):
"""Specification for the `eta` field"""
name = "eta"
dtype = float
def createField(self, sim):
sim.attrs[] = 0.16
class FLowerAt1MSUN(Spec):
"""Specification for the `f_lower_at_1MSUN` field"""
......@@ -408,16 +481,47 @@ class MeanAnomaly(Spec):
dtype = float
class MassOrdering(InterfieldSpec):
"""Specification for the ordering of fields `mass1` and `mass2`"""
name = "mass-ordering"
validMsg = "mass1 >= mass2"
invalidMsg = "mass1 < mass2"
def valid(self, sim):
mass1Valid = Mass1().valid(sim)
mass2Valid = Mass2().valid(sim)
if not isinstance(mass1Valid, err.Valid):
return err.InvalidInterfields(self)
if not isinstance(mass2Valid, err.Valid):
return err.InvalidInterfields(self)
mass1 = sim.att('mass1')
mass2 = sim.att('mass2')
if mass1 >= mass2 or isclose(mass1, mass2, rel_tol=1e-5):
return err.ValidInterfield(self)
return err.InvalidInterfield(self)
# Format 2
class Mass1VsTime(ROMSplineSpec):
"""Specification for the `mass1-vs-time` field"""
name = 'mass1-vs-time'
def createField(self, sim):
super(Mass1VsTime, self).createField(sim)
sim['mass1-vs-time/Y'][:] = np.array([4.0]*10)
class Mass2VsTime(ROMSplineSpec):
"""Specification for the `mass2-vs-time` field"""
name = 'mass2-vs-time'
def createField(self, sim):
super(Mass2VsTime, self).createField(sim)
sim['mass2-vs-time/Y'][:] = np.array([1.0]*10)
class Spin1xVsTime(ROMSplineSpec):
"""Specification for the `spin1x-vs-time` field"""
......@@ -497,6 +601,41 @@ class LNhatzVsTime(ROMSplineSpec):
class OmegaVsTime(ROMSplineSpec):
"""Specification for the `Omega-vs-time` field"""
name = 'Omega-vs-time'
invalidMsg = "Omega is not monotonic: Suggest downgrading to Format=1"
def valid(self, sim):
romSplineValid = super(OmegaVsTime, self).valid(sim)
if not isinstance(romSplineValid, err.Valid):
return romSplineValid
Omegas = sim['Omega-vs-time/Y'][:]
if ismonotonic(Omegas):
return romSplineValid
return err.InvalidSequence(self)
class MassVsTimeOrdering(InterfieldSpec):
"""Specification for the ordering of fields `mass1/mass2-vs-time``"""
name = "mass-vs-time-ordering"
validMsg = "mass1-vs-time >= mass2-vs-time"
invalidMsg = "mass1-vs-time < mass2-vs-time"
def valid(self, sim):
mass1VsTimeValid = Mass1VsTime().valid(sim)
mass2VsTimeValid = Mass2VsTime().valid(sim)
if not isinstance(mass1VsTimeValid, err.Valid):
return err.InvalidInterfields(self)
if not isinstance(mass2VsTimeValid, err.Valid):
return err.InvalidInterfields(self)
mass1 = sim['mass1-vs-time/Y'][0]
mass2 = sim['mass2-vs-time/Y'][0]
if mass1 >= mass2 or isclose(mass1, mass2, rel_tol=1e-5):
return err.ValidInterfield(self)
return err.InvalidInterfield(self)
# Format 3
This diff is collapsed.
......@@ -24,7 +24,7 @@ import numpy as np
from lvcnrpy.Sim import Sim
def lvcnrcheck(args):
def lvcnrcheck(args, returncode=False):
"""Software API adapter for `lvcnrcheck`
API to `lvcnrcheck` since `lvcnrcheck` only provides a CLI.
......@@ -35,16 +35,25 @@ def lvcnrcheck(args):
List of command line argument to call `lvcnrcheck` with. Typically this
should end with a path to a HDF5 file to validate against the LVCNR
Wavform Reopsitory format specification.
returncode: bool (optional)
If `True`, return the return code of `lvcnrcheck`. Default is `False`.
output: str
Standard output that would be recived from calling `lvcnrcheck` through
the CLI interface with `h5Path`.
(output, returncode): (str, bool)
If `returncode` then return the standard output and the return code of
`lvcnrcheck` as a tuple.
pipe = Popen(['lvcnrcheck']+args, stdout=PIPE, stderr=STDOUT)
output = pipe.communicate()[0]
return output
if not returncode:
return output
return (output, pipe.returncode)
def createValidSim():
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment