def __init__(self): """Init of Model class.""" # initiate class logger self.logger = logging.getLogger( "dipplanner.model.buhlmann.model.Model") self.logger.debug("creating an instance of Model") self.units = 'metric' self.tissues = [] self.ox_tox = OxTox() self.gradient = None self.init_gradient() # store water wapour pp self.pp_h2o = tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP) for _ in range(self.COMPS): self.tissues.append(Compartment()) self.set_time_constants() for comp in self.tissues: comp.set_pp(pp_he=0.0, pp_n2=settings.DEFAULT_AIR_F_INNERT_GAS * (settings.AMBIANT_PRESSURE_SURFACE - self.pp_h2o)) self.metadata = "(none)"
def setUp(self): """Init of the tests.""" super().setUp() # temporary hack (tests): activate_debug_for_tests() settings.RUN_TIME = True settings.SURFACE_TEMP = 12 self.ox1 = OxTox() self.ox2 = OxTox()
def __init__(self): """Init of Model class.""" # initiate class logger self.logger = logging.getLogger( "dipplanner.model.buhlmann.model.Model") self.logger.debug("creating an instance of Model") self.units = 'metric' self.tissues = [] self.ox_tox = OxTox() self.gradient = None self.init_gradient() # store water wapour pp self.pp_h2o = tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP) for _ in range(self.COMPS): self.tissues.append(Compartment()) self.set_time_constants() for comp in self.tissues: comp.set_pp(pp_he=0.0, pp_n2=settings.DEFAULT_AIR_F_INNERT_GAS * (settings.AMBIANT_PRESSURE_SURFACE - self.pp_h2o)) self.metadata = "(none)"
def __init__(self): """Constructor for model class *Keyword arguments:* <none> *Returns:* <nothing> *Raise:* <nothing> """ #initiate class logger self.logger = logging.getLogger( "dipplanner.model.buhlmann.model.Model") self.logger.debug("creating an instance of Model") self.units = 'metric' self.tissues = [] self.ox_tox = OxTox() self.gradient = None self.init_gradient() for _ in range(0, self.COMPS): self.tissues.append(Compartment()) self.set_time_constants() for comp in self.tissues: comp.set_pp(0.0, 0.79 * (settings.AMBIANT_PRESSURE_SURFACE - tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP))) #TODO: Check above : 0.79 or settings.DEFAULT_AIR_PPN2 (tdb) ? self.metadata = "(none)"
def setUp(self): """Init of the tests.""" super().setUp() # temporary hack (tests): activate_debug_for_tests() settings.RUN_TIME = True settings.SURFACE_TEMP = 12 self.ox1 = OxTox() self.ox2 = OxTox()
class Model(): """Represent a Buhlmann model. Composed of a tissue array of Compartment[] Has an OxTox and Gradient object Can throw a ModelStateException propagated from a Compartment if pressures or time is out of bounds. Models are initialised by initModel() if they are new models, or validated by validateModel() if they are rebuild from a saved model. The model is capable of ascending or descending via ascDec() using the ascDec() method of Compartment, or accounting for a constant depth using the constDepth() method of Compartment. Attributes: * tissues (list)-- a list of Compartments * gradient (Gradient) -- gradient factor object * ox_tox (OxTox) -- OxTox object * metadata (str) -- Stores infos about where the model was created * units (str) -- only 'metric' allowed * COMPS (int) -- static info : number of compartments """ COMPS = 16 def __init__(self): """Init of Model class.""" # initiate class logger self.logger = logging.getLogger( "dipplanner.model.buhlmann.model.Model") self.logger.debug("creating an instance of Model") self.units = 'metric' self.tissues = [] self.ox_tox = OxTox() self.gradient = None self.init_gradient() # store water wapour pp self.pp_h2o = tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP) for _ in range(self.COMPS): self.tissues.append(Compartment()) self.set_time_constants() for comp in self.tissues: comp.set_pp(pp_he=0.0, pp_n2=settings.DEFAULT_AIR_F_INNERT_GAS * (settings.AMBIANT_PRESSURE_SURFACE - self.pp_h2o)) self.metadata = "(none)" def __deepcopy__(self, memo): """Deepcopy method will be called by copy.deepcopy. Used for "cloning" the object into another new object. :param memo: not used here :returns: Compartment object copy of itself :rtype: :class:`Model` """ newobj = Model() newobj.units = self.units newobj.ox_tox = copy.deepcopy(self.ox_tox) newobj.gradient = copy.deepcopy(self.gradient) newobj.metadata = self.metadata for i in range(0, len(self.tissues)): newobj.tissues[i] = copy.deepcopy(self.tissues[i]) return newobj def __repr__(self): """Return a string representing the model. :returns: string representation of the model :rtype: str """ model_string = "" # "Compartment pressures:\n" for comp_number in range(0, self.COMPS): model_string += ( "C:%s He:%02.06f N2:%02.06f gf:%01.02f " "mv_at:%02.06f max_amb:%02.06f MV:%02.06f\n" % (comp_number, self.tissues[comp_number].pp_he, self.tissues[comp_number].pp_n2, self.gradient.gf, self.tissues[comp_number].get_m_value_at( settings.AMBIANT_PRESSURE_SURFACE), (self.tissues[comp_number].get_max_amb(self.gradient.gf)) * 1, self.tissues[comp_number].get_mv( settings.AMBIANT_PRESSURE_SURFACE))) # model_string += "Ceiling: %s\n" % self.ceiling() # model_string += "Max surface M-Value: %s\n" % self.m_value(0.0) # model_string += "OTUs accumulated: %s" % self.ox_tox.otu return model_string def __str__(self): """Return a human readable name of the segment. :returns: string representation of the model :rtype: str """ return self.__repr__() def init_gradient(self): """Initialise the gradient attribute. uses the default settings parameters for gf_low and high """ self.gradient = Gradient(settings.GF_LOW, settings.GF_HIGH) def set_time_constants(self, deco_model=None): """Initialize time constants in buhlmann tissue list. Only for metric values :param str deco_model: "ZHL16b" or "ZHL16c" """ # note: comparing with buhlmann original (1990) ZH-L16 a coeficient, # there is here a x10 factor for a coeficient # h_he, h_n2, a_he, b_he, a_n2, b_n2 if deco_model is None: deco_model = settings.DECO_MODEL if deco_model == "ZHL16c": self.logger.info("model used: Buhlmann ZHL16c") if settings.BUHLMANN_VALUES == '1a': self.tissues[0].set_compartment_time_constants( 001.51, 004.0, 1.7424, 0.4245, 1.2599, 0.5050) elif settings.BUHLMANN_VALUES == '1b': self.tissues[0].set_compartment_time_constants( 001.88, 005.0, 1.6189, 0.4770, 1.1696, 0.5578) self.tissues[1].set_compartment_time_constants( 003.02, 008.0, 1.3830, 0.5747, 1.0000, 0.6514) self.tissues[2].set_compartment_time_constants( 004.72, 012.5, 1.1919, 0.6527, 0.8618, 0.7222) self.tissues[3].set_compartment_time_constants( 006.99, 018.5, 1.0458, 0.7223, 0.7562, 0.7825) self.tissues[4].set_compartment_time_constants( 010.21, 027.0, 0.9220, 0.7582, 0.6200, 0.8126) self.tissues[5].set_compartment_time_constants( 014.48, 038.3, 0.8205, 0.7957, 0.5043, 0.8434) self.tissues[6].set_compartment_time_constants( 020.53, 054.3, 0.7305, 0.8279, 0.4410, 0.8693) self.tissues[7].set_compartment_time_constants( 029.11, 077.0, 0.6502, 0.8553, 0.4000, 0.8910) self.tissues[8].set_compartment_time_constants( 041.20, 109.0, 0.5950, 0.8757, 0.3750, 0.9092) self.tissues[9].set_compartment_time_constants( 055.19, 146.0, 0.5545, 0.8903, 0.3500, 0.9222) self.tissues[10].set_compartment_time_constants( 070.69, 187.0, 0.5333, 0.8997, 0.3295, 0.9319) self.tissues[11].set_compartment_time_constants( 090.34, 239.0, 0.5189, 0.9073, 0.3065, 0.9403) self.tissues[12].set_compartment_time_constants( 115.29, 305.0, 0.5181, 0.9122, 0.2835, 0.9477) self.tissues[13].set_compartment_time_constants( 147.42, 390.0, 0.5176, 0.9171, 0.2610, 0.9544) self.tissues[14].set_compartment_time_constants( 188.24, 498.0, 0.5172, 0.9217, 0.2480, 0.9602) self.tissues[15].set_compartment_time_constants( 240.03, 635.0, 0.5119, 0.9267, 0.2327, 0.9653) elif deco_model == "ZHL16b": self.logger.info("model used: Buhlmann ZHL16b") if settings.BUHLMANN_VALUES == '1a': self.tissues[0].set_compartment_time_constants( 001.51, 004.0, 1.7424, 0.4245, 1.2599, 0.5050) elif settings.BUHLMANN_VALUES == '1b': self.tissues[0].set_compartment_time_constants( 001.88, 005.0, 1.6189, 0.4770, 1.1696, 0.5578) self.tissues[1].set_compartment_time_constants( 003.02, 008.0, 1.3830, 0.5747, 1.0000, 0.6514) self.tissues[2].set_compartment_time_constants( 004.72, 012.5, 1.1919, 0.6527, 0.8618, 0.7222) self.tissues[3].set_compartment_time_constants( 006.99, 018.5, 1.0458, 0.7223, 0.7562, 0.7825) self.tissues[4].set_compartment_time_constants( 010.21, 027.0, 0.9220, 0.7582, 0.6667, 0.8126) self.tissues[5].set_compartment_time_constants( 014.48, 038.3, 0.8205, 0.7957, 0.5600, 0.8434) self.tissues[6].set_compartment_time_constants( 020.53, 054.3, 0.7305, 0.8279, 0.4947, 0.8693) self.tissues[7].set_compartment_time_constants( 029.11, 077.0, 0.6502, 0.8553, 0.4500, 0.8910) self.tissues[8].set_compartment_time_constants( 041.20, 109.0, 0.5950, 0.8757, 0.4187, 0.9092) self.tissues[9].set_compartment_time_constants( 055.19, 146.0, 0.5545, 0.8903, 0.3798, 0.9222) self.tissues[10].set_compartment_time_constants( 070.69, 187.0, 0.5333, 0.8997, 0.3497, 0.9319) self.tissues[11].set_compartment_time_constants( 090.34, 239.0, 0.5189, 0.9073, 0.3223, 0.9403) self.tissues[12].set_compartment_time_constants( 115.29, 305.0, 0.5181, 0.9122, 0.2850, 0.9477) self.tissues[13].set_compartment_time_constants( 147.42, 390.0, 0.5176, 0.9171, 0.2737, 0.9544) self.tissues[14].set_compartment_time_constants( 188.24, 498.0, 0.5172, 0.9217, 0.2523, 0.9602) self.tissues[15].set_compartment_time_constants( 240.03, 635.0, 0.5119, 0.9267, 0.2327, 0.9653) elif deco_model == "ZHL16a": self.logger.info("model used: Buhlmann ZHL16a") if settings.BUHLMANN_VALUES == '1a': self.tissues[0].set_compartment_time_constants( 001.51, 004.0, 1.7424, 0.4245, 1.2599, 0.5050) elif settings.BUHLMANN_VALUES == '1b': self.tissues[0].set_compartment_time_constants( 001.88, 005.0, 1.6189, 0.4770, 1.1696, 0.5578) self.tissues[1].set_compartment_time_constants( 003.02, 008.0, 1.3830, 0.5747, 1.0000, 0.6514) self.tissues[2].set_compartment_time_constants( 004.72, 012.5, 1.1919, 0.6527, 0.8618, 0.7222) self.tissues[3].set_compartment_time_constants( 006.99, 018.5, 1.0458, 0.7223, 0.7562, 0.7825) self.tissues[4].set_compartment_time_constants( 010.21, 027.0, 0.9220, 0.7582, 0.6667, 0.8126) self.tissues[5].set_compartment_time_constants( 014.48, 038.3, 0.8205, 0.7957, 0.5933, 0.8434) self.tissues[6].set_compartment_time_constants( 020.53, 054.3, 0.7305, 0.8279, 0.5282, 0.8693) self.tissues[7].set_compartment_time_constants( 029.11, 077.0, 0.6502, 0.8553, 0.4710, 0.8910) self.tissues[8].set_compartment_time_constants( 041.20, 109.0, 0.5950, 0.8757, 0.4187, 0.9092) self.tissues[9].set_compartment_time_constants( 055.19, 146.0, 0.5545, 0.8903, 0.3798, 0.9222) self.tissues[10].set_compartment_time_constants( 070.69, 187.0, 0.5333, 0.8997, 0.3497, 0.9319) self.tissues[11].set_compartment_time_constants( 090.34, 239.0, 0.5189, 0.9073, 0.3223, 0.9403) self.tissues[12].set_compartment_time_constants( 115.29, 305.0, 0.5181, 0.9122, 0.2971, 0.9477) self.tissues[13].set_compartment_time_constants( 147.42, 390.0, 0.5176, 0.9171, 0.2737, 0.9544) self.tissues[14].set_compartment_time_constants( 188.24, 498.0, 0.5172, 0.9217, 0.2523, 0.9602) self.tissues[15].set_compartment_time_constants( 240.03, 635.0, 0.5119, 0.9267, 0.2327, 0.9653) def validate_model(self): """Validate model - checks over the model and looks for corruption. This is needed to check a model that has been loaded from XML Resets time constants :returns: True if OK :rtype: bool :raises ModelValidationException: when validation failed. """ time_constant_zero = False # need for resetting time constants for comp in self.tissues: if comp.pp_n2 <= 0.0: raise ModelValidationException("pp_N2 < 0 in compartment") if comp.pp_he < 0.0: raise ModelValidationException("pp_He < 0 in compartment") if 0.0 in (comp.k_he, comp.k_n2, comp.a_he, comp.b_he, comp.a_n2, comp.b_n2): time_constant_zero = True if time_constant_zero: self.set_time_constants() return True def control_compartment(self): """Determine the controlling compartment at ceiling (1-16). :returns: reference number of the controlling compartment (between 1 to 16) :rtype: int """ control_compartment_number = 0 max_pressure = 0.0 for comp_number in range(0, self.COMPS): pressure = self.tissues[comp_number].get_max_amb( self.gradient.gf) - settings.AMBIANT_PRESSURE_SURFACE # self.logger.debug("pressure:%s" % pressure) if pressure > max_pressure: control_compartment_number = comp_number max_pressure = pressure return control_compartment_number + 1 def ceiling(self): """Determine the current ceiling depth. :returns: ceiling depth in meter :rtype: float """ pressure = 0.0 for comp in self.tissues: # Get compartment tolerated ambient pressure and convert from # absolute pressure to depth comp_pressure = comp.get_max_amb( self.gradient.gf) - settings.AMBIANT_PRESSURE_SURFACE if comp_pressure > pressure: pressure = comp_pressure return tools.pressure_to_depth(pressure) def ceiling_in_pabs(self): """Determine the current ceiling. :returns: ceiling in bar (absolute pressure) :rtype: float """ pressure = 0.0 for comp in self.tissues: # Get compartment tolerated ambient pressure and convert from # absolute pressure to depth comp_pressure = comp.get_max_amb(self.gradient.gf) if comp_pressure > pressure: pressure = comp_pressure return pressure def m_value(self, pressure): """Determine the maximum M-Value for a given depth (pressure). :param float pressure: in bar :returns: max M-Value :rtype: float """ p_absolute = pressure + settings.AMBIANT_PRESSURE_SURFACE compartment_mv = 0.0 max_mv = 0.0 for comp in self.tissues: compartment_mv = comp.get_mv(p_absolute) if compartment_mv > max_mv: max_mv = compartment_mv # self.logger.debug("max mv : %s" % max_mv) return max_mv def const_depth(self, pressure, seg_time, f_he, f_n2, pp_o2): """Constant depth profile. Calls Compartment.constDepth for each compartment to update the model. :param float pressure: pressure of this depth of segment in bar :param float seg_time: time of segment in seconds :param float f_he: fraction of inert gas Helium in inspired gas mix :param float f_n2: fraction of inert gas Nitrogen in inspired gas mix :param float pp_o2: for CCR mode, partial pressure of oxygen in bar. if == 0.0, then: open circuit :raises ModelStateException: """ ambiant_pressure = pressure + settings.AMBIANT_PRESSURE_SURFACE if pp_o2 > 0.0: # CCR mode # Determine pInert by subtracting absolute oxygen pressure and pH2O # Note that if f_he and f_n2 == 0.0 then need to force pp's to zero if f_he + f_n2 > 0.0: p_inert = (ambiant_pressure - pp_o2 - self.pp_h2o) else: p_inert = 0.0 # Verify that pInert is positive. If the setpoint is close to or # less than the depth then there is no inert gas. if p_inert > 0.0: pp_he_inspired = (p_inert * f_he) / (f_he + f_n2) pp_n2_inspired = (p_inert * f_n2) / (f_he + f_n2) else: pp_he_inspired = 0.0 pp_n2_inspired = 0.0 # update OxTox object pp_o2_inspired = pp_o2 # Check that ppO2Inspired is not greater than the depth. # This occurs in shallow deco when the setpoint specified is >depth if pp_o2_inspired <= ambiant_pressure and p_inert > 0.0: # pp_o2 is the setpoint self.ox_tox.add_o2(seg_time, pp_o2) else: # pp_o2 is equal to the depth, also true is there is # no inert gaz self.ox_tox.add_o2(seg_time, ambiant_pressure - self.pp_h2o) else: # OC mode pp_he_inspired = (ambiant_pressure - self.pp_h2o) * f_he pp_n2_inspired = (ambiant_pressure - self.pp_h2o) * f_n2 # update ox_tox if pressure == 0.0: # surface self.ox_tox.remove_o2(seg_time) else: self.ox_tox.add_o2(seg_time, (ambiant_pressure - self.pp_h2o) * (1.0 - f_he - f_n2)) if seg_time > 0: print(seg_time) for comp in self.tissues: comp.const_depth(pp_he_inspired, pp_n2_inspired, seg_time) def asc_desc(self, start, finish, rate, f_he, f_n2, pp_o2): """Ascend/Descend profile. Calls Compartment.asc_desc to update compartments :param float start: start pressure of this segment in bar (WARNING: not meter ! it's a pressure) :param float finish: finish pressure of this segment in bar (WARNING: not meter ! it's a pressure) :param float rate: rate of ascent or descent in m/s :param float f_he: Fraction of inert gas Helium in inspired gas mix :param float f_n2: Fraction of inert gas Nitrogen in inspired gas mix :param float pp_o2: for CCR mode, partial pressure of oxygen in bar. if == 0.0, then: open circuit :raises ModelStateException: """ # rem: here we do not bother of PP_H2O like in constant_depth : WHY ? start_ambiant_pressure = start + settings.AMBIANT_PRESSURE_SURFACE finish_ambiant_pressure = finish + settings.AMBIANT_PRESSURE_SURFACE # here we have seg_time in min and rate in m/min # rate should be in bar/min (or bar/s), not m/min nor m/sec rate = tools.depth_to_pressure(rate) seg_time = abs((finish - start) / rate) if pp_o2 > 0.0: # CCR mode # Calculate inert gas partial pressure == pAmb - pO2 - pH2O p_inert_start = (start_ambiant_pressure - pp_o2 - self.pp_h2o) p_inert_finish = (finish_ambiant_pressure - pp_o2 - self.pp_h2o) # Check that it doesn't go less than zero. # Could be due to shallow deco or starting on high setpoint if p_inert_start < 0.0: p_inert_start = 0.0 if p_inert_finish < 0.0: p_inert_finish = 0.0 # Separate into He and N2 components, checking that we are not # on pure O2 (or we get an arithmetic error) if f_he + f_n2 > 0.0: pp_he_inspired = (p_inert_start * f_he) / (f_he + f_n2) pp_n2_inspired = (p_inert_start * f_n2) / (f_he + f_n2) # calculate rate of change of each inert gas rate_he = ((p_inert_finish * f_he) / (f_he + f_n2) - pp_he_inspired) / (seg_time) rate_n2 = ((p_inert_finish * f_n2) / (f_he + f_n2) - pp_n2_inspired) / (seg_time) else: pp_he_inspired = 0.0 pp_n2_inspired = 0.0 rate_he = 0.0 rate_n2 = 0.0 # update ox_tox, constant pp_o2 self.ox_tox.add_o2(seg_time, pp_o2) else: # OC mode # calculate He and N2 components pp_he_inspired = (start_ambiant_pressure - self.pp_h2o) * f_he pp_n2_inspired = (start_ambiant_pressure - self.pp_h2o) * f_n2 rate_he = rate * f_he rate_n2 = rate * f_n2 # update ox_tox, use average pp_o2 pp_o2_inspired_avg = ( (start_ambiant_pressure - finish_ambiant_pressure) / 2 + finish_ambiant_pressure - self.pp_h2o) * (1.0 - f_he - f_n2) self.ox_tox.add_o2(seg_time, pp_o2_inspired_avg) for comp in self.tissues: comp.asc_desc(pp_he_inspired, pp_n2_inspired, rate_he, rate_n2, seg_time)
class Model(): """Represent a Buhlmann model. Composed of a tissue array of Compartment[] Has an OxTox and Gradient object Can throw a ModelStateException propagated from a Compartment if pressures or time is out of bounds. Models are initialised by initModel() if they are new models, or validated by validateModel() if they are rebuild from a saved model. The model is capable of ascending or descending via ascDec() using the ascDec() method of Compartment, or accounting for a constant depth using the constDepth() method of Compartment. Attributes: * tissues (list)-- a list of Compartments * gradient (Gradient) -- gradient factor object * ox_tox (OxTox) -- OxTox object * metadata (str) -- Stores infos about where the model was created * units (str) -- only 'metric' allowed * COMPS (int) -- static info : number of compartments """ COMPS = 16 def __init__(self): """Init of Model class.""" # initiate class logger self.logger = logging.getLogger( "dipplanner.model.buhlmann.model.Model") self.logger.debug("creating an instance of Model") self.units = 'metric' self.tissues = [] self.ox_tox = OxTox() self.gradient = None self.init_gradient() # store water wapour pp self.pp_h2o = tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP) for _ in range(self.COMPS): self.tissues.append(Compartment()) self.set_time_constants() for comp in self.tissues: comp.set_pp(pp_he=0.0, pp_n2=settings.DEFAULT_AIR_F_INNERT_GAS * (settings.AMBIANT_PRESSURE_SURFACE - self.pp_h2o)) self.metadata = "(none)" def __deepcopy__(self, memo): """Deepcopy method will be called by copy.deepcopy. Used for "cloning" the object into another new object. :param memo: not used here :returns: Compartment object copy of itself :rtype: :class:`Model` """ newobj = Model() newobj.units = self.units newobj.ox_tox = copy.deepcopy(self.ox_tox) newobj.gradient = copy.deepcopy(self.gradient) newobj.metadata = self.metadata for i in range(0, len(self.tissues)): newobj.tissues[i] = copy.deepcopy(self.tissues[i]) return newobj def __repr__(self): """Return a string representing the model. :returns: string representation of the model :rtype: str """ model_string = "" # "Compartment pressures:\n" for comp_number in range(0, self.COMPS): model_string += ("C:%s He:%02.06f N2:%02.06f gf:%01.02f " "mv_at:%02.06f max_amb:%02.06f MV:%02.06f\n" % ( comp_number, self.tissues[comp_number].pp_he, self.tissues[comp_number].pp_n2, self.gradient.gf, self.tissues[comp_number].get_m_value_at( settings.AMBIANT_PRESSURE_SURFACE), (self.tissues[comp_number].get_max_amb( self.gradient.gf)) * 1, self.tissues[comp_number].get_mv( settings.AMBIANT_PRESSURE_SURFACE))) # model_string += "Ceiling: %s\n" % self.ceiling() # model_string += "Max surface M-Value: %s\n" % self.m_value(0.0) # model_string += "OTUs accumulated: %s" % self.ox_tox.otu return model_string def __str__(self): """Return a human readable name of the segment. :returns: string representation of the model :rtype: str """ return self.__repr__() def init_gradient(self): """Initialise the gradient attribute. uses the default settings parameters for gf_low and high """ self.gradient = Gradient(settings.GF_LOW, settings.GF_HIGH) def set_time_constants(self, deco_model=None): """Initialize time constants in buhlmann tissue list. Only for metric values :param str deco_model: "ZHL16b" or "ZHL16c" """ # note: comparing with buhlmann original (1990) ZH-L16 a coeficient, # there is here a x10 factor for a coeficient # h_he, h_n2, a_he, b_he, a_n2, b_n2 if deco_model is None: deco_model = settings.DECO_MODEL if deco_model == "ZHL16c": self.logger.info("model used: Buhlmann ZHL16c") if settings.BUHLMANN_VALUES == '1a': self.tissues[0].set_compartment_time_constants( 001.51, 004.0, 1.7424, 0.4245, 1.2599, 0.5050) elif settings.BUHLMANN_VALUES == '1b': self.tissues[0].set_compartment_time_constants( 001.88, 005.0, 1.6189, 0.4770, 1.1696, 0.5578) self.tissues[1].set_compartment_time_constants( 003.02, 008.0, 1.3830, 0.5747, 1.0000, 0.6514) self.tissues[2].set_compartment_time_constants( 004.72, 012.5, 1.1919, 0.6527, 0.8618, 0.7222) self.tissues[3].set_compartment_time_constants( 006.99, 018.5, 1.0458, 0.7223, 0.7562, 0.7825) self.tissues[4].set_compartment_time_constants( 010.21, 027.0, 0.9220, 0.7582, 0.6200, 0.8126) self.tissues[5].set_compartment_time_constants( 014.48, 038.3, 0.8205, 0.7957, 0.5043, 0.8434) self.tissues[6].set_compartment_time_constants( 020.53, 054.3, 0.7305, 0.8279, 0.4410, 0.8693) self.tissues[7].set_compartment_time_constants( 029.11, 077.0, 0.6502, 0.8553, 0.4000, 0.8910) self.tissues[8].set_compartment_time_constants( 041.20, 109.0, 0.5950, 0.8757, 0.3750, 0.9092) self.tissues[9].set_compartment_time_constants( 055.19, 146.0, 0.5545, 0.8903, 0.3500, 0.9222) self.tissues[10].set_compartment_time_constants( 070.69, 187.0, 0.5333, 0.8997, 0.3295, 0.9319) self.tissues[11].set_compartment_time_constants( 090.34, 239.0, 0.5189, 0.9073, 0.3065, 0.9403) self.tissues[12].set_compartment_time_constants( 115.29, 305.0, 0.5181, 0.9122, 0.2835, 0.9477) self.tissues[13].set_compartment_time_constants( 147.42, 390.0, 0.5176, 0.9171, 0.2610, 0.9544) self.tissues[14].set_compartment_time_constants( 188.24, 498.0, 0.5172, 0.9217, 0.2480, 0.9602) self.tissues[15].set_compartment_time_constants( 240.03, 635.0, 0.5119, 0.9267, 0.2327, 0.9653) elif deco_model == "ZHL16b": self.logger.info("model used: Buhlmann ZHL16b") if settings.BUHLMANN_VALUES == '1a': self.tissues[0].set_compartment_time_constants( 001.51, 004.0, 1.7424, 0.4245, 1.2599, 0.5050) elif settings.BUHLMANN_VALUES == '1b': self.tissues[0].set_compartment_time_constants( 001.88, 005.0, 1.6189, 0.4770, 1.1696, 0.5578) self.tissues[1].set_compartment_time_constants( 003.02, 008.0, 1.3830, 0.5747, 1.0000, 0.6514) self.tissues[2].set_compartment_time_constants( 004.72, 012.5, 1.1919, 0.6527, 0.8618, 0.7222) self.tissues[3].set_compartment_time_constants( 006.99, 018.5, 1.0458, 0.7223, 0.7562, 0.7825) self.tissues[4].set_compartment_time_constants( 010.21, 027.0, 0.9220, 0.7582, 0.6667, 0.8126) self.tissues[5].set_compartment_time_constants( 014.48, 038.3, 0.8205, 0.7957, 0.5600, 0.8434) self.tissues[6].set_compartment_time_constants( 020.53, 054.3, 0.7305, 0.8279, 0.4947, 0.8693) self.tissues[7].set_compartment_time_constants( 029.11, 077.0, 0.6502, 0.8553, 0.4500, 0.8910) self.tissues[8].set_compartment_time_constants( 041.20, 109.0, 0.5950, 0.8757, 0.4187, 0.9092) self.tissues[9].set_compartment_time_constants( 055.19, 146.0, 0.5545, 0.8903, 0.3798, 0.9222) self.tissues[10].set_compartment_time_constants( 070.69, 187.0, 0.5333, 0.8997, 0.3497, 0.9319) self.tissues[11].set_compartment_time_constants( 090.34, 239.0, 0.5189, 0.9073, 0.3223, 0.9403) self.tissues[12].set_compartment_time_constants( 115.29, 305.0, 0.5181, 0.9122, 0.2850, 0.9477) self.tissues[13].set_compartment_time_constants( 147.42, 390.0, 0.5176, 0.9171, 0.2737, 0.9544) self.tissues[14].set_compartment_time_constants( 188.24, 498.0, 0.5172, 0.9217, 0.2523, 0.9602) self.tissues[15].set_compartment_time_constants( 240.03, 635.0, 0.5119, 0.9267, 0.2327, 0.9653) elif deco_model == "ZHL16a": self.logger.info("model used: Buhlmann ZHL16a") if settings.BUHLMANN_VALUES == '1a': self.tissues[0].set_compartment_time_constants( 001.51, 004.0, 1.7424, 0.4245, 1.2599, 0.5050) elif settings.BUHLMANN_VALUES == '1b': self.tissues[0].set_compartment_time_constants( 001.88, 005.0, 1.6189, 0.4770, 1.1696, 0.5578) self.tissues[1].set_compartment_time_constants( 003.02, 008.0, 1.3830, 0.5747, 1.0000, 0.6514) self.tissues[2].set_compartment_time_constants( 004.72, 012.5, 1.1919, 0.6527, 0.8618, 0.7222) self.tissues[3].set_compartment_time_constants( 006.99, 018.5, 1.0458, 0.7223, 0.7562, 0.7825) self.tissues[4].set_compartment_time_constants( 010.21, 027.0, 0.9220, 0.7582, 0.6667, 0.8126) self.tissues[5].set_compartment_time_constants( 014.48, 038.3, 0.8205, 0.7957, 0.5933, 0.8434) self.tissues[6].set_compartment_time_constants( 020.53, 054.3, 0.7305, 0.8279, 0.5282, 0.8693) self.tissues[7].set_compartment_time_constants( 029.11, 077.0, 0.6502, 0.8553, 0.4710, 0.8910) self.tissues[8].set_compartment_time_constants( 041.20, 109.0, 0.5950, 0.8757, 0.4187, 0.9092) self.tissues[9].set_compartment_time_constants( 055.19, 146.0, 0.5545, 0.8903, 0.3798, 0.9222) self.tissues[10].set_compartment_time_constants( 070.69, 187.0, 0.5333, 0.8997, 0.3497, 0.9319) self.tissues[11].set_compartment_time_constants( 090.34, 239.0, 0.5189, 0.9073, 0.3223, 0.9403) self.tissues[12].set_compartment_time_constants( 115.29, 305.0, 0.5181, 0.9122, 0.2971, 0.9477) self.tissues[13].set_compartment_time_constants( 147.42, 390.0, 0.5176, 0.9171, 0.2737, 0.9544) self.tissues[14].set_compartment_time_constants( 188.24, 498.0, 0.5172, 0.9217, 0.2523, 0.9602) self.tissues[15].set_compartment_time_constants( 240.03, 635.0, 0.5119, 0.9267, 0.2327, 0.9653) def validate_model(self): """Validate model - checks over the model and looks for corruption. This is needed to check a model that has been loaded from XML Resets time constants :returns: True if OK :rtype: bool :raises ModelValidationException: when validation failed. """ time_constant_zero = False # need for resetting time constants for comp in self.tissues: if comp.pp_n2 <= 0.0: raise ModelValidationException("pp_N2 < 0 in compartment") if comp.pp_he < 0.0: raise ModelValidationException("pp_He < 0 in compartment") if 0.0 in (comp.k_he, comp.k_n2, comp.a_he, comp.b_he, comp.a_n2, comp.b_n2): time_constant_zero = True if time_constant_zero: self.set_time_constants() return True def control_compartment(self): """Determine the controlling compartment at ceiling (1-16). :returns: reference number of the controlling compartment (between 1 to 16) :rtype: int """ control_compartment_number = 0 max_pressure = 0.0 for comp_number in range(0, self.COMPS): pressure = self.tissues[comp_number].get_max_amb( self.gradient.gf) - settings.AMBIANT_PRESSURE_SURFACE # self.logger.debug("pressure:%s" % pressure) if pressure > max_pressure: control_compartment_number = comp_number max_pressure = pressure return control_compartment_number + 1 def ceiling(self): """Determine the current ceiling depth. :returns: ceiling depth in meter :rtype: float """ pressure = 0.0 for comp in self.tissues: # Get compartment tolerated ambient pressure and convert from # absolute pressure to depth comp_pressure = comp.get_max_amb( self.gradient.gf) - settings.AMBIANT_PRESSURE_SURFACE if comp_pressure > pressure: pressure = comp_pressure return tools.pressure_to_depth(pressure) def ceiling_in_pabs(self): """Determine the current ceiling. :returns: ceiling in bar (absolute pressure) :rtype: float """ pressure = 0.0 for comp in self.tissues: # Get compartment tolerated ambient pressure and convert from # absolute pressure to depth comp_pressure = comp.get_max_amb(self.gradient.gf) if comp_pressure > pressure: pressure = comp_pressure return pressure def m_value(self, pressure): """Determine the maximum M-Value for a given depth (pressure). :param float pressure: in bar :returns: max M-Value :rtype: float """ p_absolute = pressure + settings.AMBIANT_PRESSURE_SURFACE compartment_mv = 0.0 max_mv = 0.0 for comp in self.tissues: compartment_mv = comp.get_mv(p_absolute) if compartment_mv > max_mv: max_mv = compartment_mv # self.logger.debug("max mv : %s" % max_mv) return max_mv def const_depth(self, pressure, seg_time, f_he, f_n2, pp_o2): """Constant depth profile. Calls Compartment.constDepth for each compartment to update the model. :param float pressure: pressure of this depth of segment in bar :param float seg_time: time of segment in seconds :param float f_he: fraction of inert gas Helium in inspired gas mix :param float f_n2: fraction of inert gas Nitrogen in inspired gas mix :param float pp_o2: for CCR mode, partial pressure of oxygen in bar. if == 0.0, then: open circuit :raises ModelStateException: """ ambiant_pressure = pressure + settings.AMBIANT_PRESSURE_SURFACE if pp_o2 > 0.0: # CCR mode # Determine pInert by subtracting absolute oxygen pressure and pH2O # Note that if f_he and f_n2 == 0.0 then need to force pp's to zero if f_he + f_n2 > 0.0: p_inert = (ambiant_pressure - pp_o2 - self.pp_h2o) else: p_inert = 0.0 # Verify that pInert is positive. If the setpoint is close to or # less than the depth then there is no inert gas. if p_inert > 0.0: pp_he_inspired = (p_inert * f_he) / (f_he + f_n2) pp_n2_inspired = (p_inert * f_n2) / (f_he + f_n2) else: pp_he_inspired = 0.0 pp_n2_inspired = 0.0 # update OxTox object pp_o2_inspired = pp_o2 # Check that ppO2Inspired is not greater than the depth. # This occurs in shallow deco when the setpoint specified is >depth if pp_o2_inspired <= ambiant_pressure and p_inert > 0.0: # pp_o2 is the setpoint self.ox_tox.add_o2(seg_time, pp_o2) else: # pp_o2 is equal to the depth, also true is there is # no inert gaz self.ox_tox.add_o2(seg_time, ambiant_pressure - self.pp_h2o) else: # OC mode pp_he_inspired = (ambiant_pressure - self.pp_h2o) * f_he pp_n2_inspired = (ambiant_pressure - self.pp_h2o) * f_n2 # update ox_tox if pressure == 0.0: # surface self.ox_tox.remove_o2(seg_time) else: self.ox_tox.add_o2( seg_time, (ambiant_pressure - self.pp_h2o) * (1.0 - f_he - f_n2)) if seg_time > 0: print(seg_time) for comp in self.tissues: comp.const_depth(pp_he_inspired, pp_n2_inspired, seg_time) def asc_desc(self, start, finish, rate, f_he, f_n2, pp_o2): """Ascend/Descend profile. Calls Compartment.asc_desc to update compartments :param float start: start pressure of this segment in bar (WARNING: not meter ! it's a pressure) :param float finish: finish pressure of this segment in bar (WARNING: not meter ! it's a pressure) :param float rate: rate of ascent or descent in m/s :param float f_he: Fraction of inert gas Helium in inspired gas mix :param float f_n2: Fraction of inert gas Nitrogen in inspired gas mix :param float pp_o2: for CCR mode, partial pressure of oxygen in bar. if == 0.0, then: open circuit :raises ModelStateException: """ # rem: here we do not bother of PP_H2O like in constant_depth : WHY ? start_ambiant_pressure = start + settings.AMBIANT_PRESSURE_SURFACE finish_ambiant_pressure = finish + settings.AMBIANT_PRESSURE_SURFACE # here we have seg_time in min and rate in m/min # rate should be in bar/min (or bar/s), not m/min nor m/sec rate = tools.depth_to_pressure(rate) seg_time = abs((finish - start) / rate) if pp_o2 > 0.0: # CCR mode # Calculate inert gas partial pressure == pAmb - pO2 - pH2O p_inert_start = (start_ambiant_pressure - pp_o2 - self.pp_h2o) p_inert_finish = (finish_ambiant_pressure - pp_o2 - self.pp_h2o) # Check that it doesn't go less than zero. # Could be due to shallow deco or starting on high setpoint if p_inert_start < 0.0: p_inert_start = 0.0 if p_inert_finish < 0.0: p_inert_finish = 0.0 # Separate into He and N2 components, checking that we are not # on pure O2 (or we get an arithmetic error) if f_he + f_n2 > 0.0: pp_he_inspired = (p_inert_start * f_he) / (f_he + f_n2) pp_n2_inspired = (p_inert_start * f_n2) / (f_he + f_n2) # calculate rate of change of each inert gas rate_he = ((p_inert_finish * f_he) / (f_he + f_n2) - pp_he_inspired) / (seg_time) rate_n2 = ((p_inert_finish * f_n2) / (f_he + f_n2) - pp_n2_inspired) / (seg_time) else: pp_he_inspired = 0.0 pp_n2_inspired = 0.0 rate_he = 0.0 rate_n2 = 0.0 # update ox_tox, constant pp_o2 self.ox_tox.add_o2(seg_time, pp_o2) else: # OC mode # calculate He and N2 components pp_he_inspired = (start_ambiant_pressure - self.pp_h2o) * f_he pp_n2_inspired = (start_ambiant_pressure - self.pp_h2o) * f_n2 rate_he = rate * f_he rate_n2 = rate * f_n2 # update ox_tox, use average pp_o2 pp_o2_inspired_avg = ( (start_ambiant_pressure - finish_ambiant_pressure) / 2 + finish_ambiant_pressure - self.pp_h2o) * (1.0 - f_he - f_n2) self.ox_tox.add_o2(seg_time, pp_o2_inspired_avg) for comp in self.tissues: comp.asc_desc(pp_he_inspired, pp_n2_inspired, rate_he, rate_n2, seg_time)
class TestModelBuhlmannOxTox(unittest.TestCase): """Test the Oxygen toxicity model.""" def setUp(self): """Init of the tests.""" super().setUp() # temporary hack (tests): activate_debug_for_tests() settings.RUN_TIME = True settings.SURFACE_TEMP = 12 self.ox1 = OxTox() self.ox2 = OxTox() def test_cns(self): """test cns.""" self.assertEqual(self.ox1.cns, 0.0, "bad cns value : %s" % self.ox1.cns) def test_otu(self): """test otu.""" self.assertEqual(self.ox1.otu, 0.0, "bad otu value : %s" % self.ox1.otu) def test_maxox(self): """test maxox.""" self.assertEqual(self.ox1.max_ox, 0.0, "bad max_ox value : %s" % self.ox1.max_ox) def test_cns2(self): """test cns 2.""" self.ox1.add_o2(10 * 60, 1.3) self.assertEqual(round(self.ox1.cns, 13), 0.0555555555556, "bad cns value : %s" % self.ox1.cns) def test_otu2(self): """test otu 2.""" self.ox1.add_o2(10 * 60, 1.3) self.assertEqual(round(self.ox1.otu, 10), 14.7944872366, "bad otu value : %s" % self.ox1.otu) def test_cns3(self): """test cns 3.""" self.ox1.add_o2(10 * 60, 1.3) self.ox1.remove_o2(4 * 60 * 60) self.assertEqual(round(self.ox1.cns, 14), 0.00874945594818, "bad cns value : %s" % self.ox1.cns) def test_otu3(self): """test otu 3.""" self.ox1.add_o2(10 * 60, 1.3) self.ox1.remove_o2(4 * 60 * 60) self.assertEqual(round(self.ox1.otu, 10), 14.7944872366, "bad otu value : %s" % self.ox1.otu) def test_cns4(self): """test cns 4.""" self.ox1.add_o2(10 * 60, 1.3) self.ox1.remove_o2(25 * 60 * 60) self.assertEqual(round(self.ox1.cns, 7), 0.0000005, "bad cns value : %s" % round(self.ox1.cns, 7)) def test_otu4(self): """test otu 4.""" self.ox1.add_o2(10 * 60, 1.3) self.ox1.remove_o2(25 * 60 * 60) self.assertEqual(round(self.ox1.otu, 11), 0.0, "bad otu value : %s" % self.ox1.otu)
class TestModelBuhlmannOxTox(unittest.TestCase): """Test the Oxygen toxicity model.""" def setUp(self): """Init of the tests.""" super().setUp() # temporary hack (tests): activate_debug_for_tests() settings.RUN_TIME = True settings.SURFACE_TEMP = 12 self.ox1 = OxTox() self.ox2 = OxTox() def test_cns(self): """test cns.""" self.assertEqual(self.ox1.cns, 0.0, "bad cns value : %s" % self.ox1.cns) def test_otu(self): """test otu.""" self.assertEqual(self.ox1.otu, 0.0, "bad otu value : %s" % self.ox1.otu) def test_maxox(self): """test maxox.""" self.assertEqual(self.ox1.max_ox, 0.0, "bad max_ox value : %s" % self.ox1.max_ox) def test_cns2(self): """test cns 2.""" self.ox1.add_o2(10 * 60, 1.3) self.assertEqual(round(self.ox1.cns, 13), 0.0555555555556, "bad cns value : %s" % self.ox1.cns) def test_otu2(self): """test otu 2.""" self.ox1.add_o2(10 * 60, 1.3) self.assertEqual(round(self.ox1.otu, 10), 14.7944872366, "bad otu value : %s" % self.ox1.otu) def test_cns3(self): """test cns 3.""" self.ox1.add_o2(10 * 60, 1.3) self.ox1.remove_o2(4 * 60 * 60) self.assertEqual(round(self.ox1.cns, 14), 0.00874945594818, "bad cns value : %s" % self.ox1.cns) def test_otu3(self): """test otu 3.""" self.ox1.add_o2(10 * 60, 1.3) self.ox1.remove_o2(4 * 60 * 60) self.assertEqual(round(self.ox1.otu, 10), 14.7944872366, "bad otu value : %s" % self.ox1.otu) def test_cns4(self): """test cns 4.""" self.ox1.add_o2(10 * 60, 1.3) self.ox1.remove_o2(25 * 60 * 60) self.assertEqual(round(self.ox1.cns, 7), 0.0000005, "bad cns value : %s" % round(self.ox1.cns, 7)) def test_otu4(self): """test otu 4.""" self.ox1.add_o2(10 * 60, 1.3) self.ox1.remove_o2(25 * 60 * 60) self.assertEqual(round(self.ox1.otu, 11), 0.0, "bad otu value : %s" % self.ox1.otu)
class Model(object): """Represents a Buhlmann model. Composed of a tissue array of Compartment[] Has an OxTox and Gradient object Can throw a ModelStateException propagated from a Compartment if pressures or time is out of bounds. Models are initialised by initModel() if they are new models, or validated by validateModel() if they are rebuild from a saved model. The model is capable of ascending or descending via ascDec() using the ascDec() method of Compartment, or accounting for a constant depth using the constDepth() method of Compartment. Attributes: * tissues (list)-- a list of Compartments * gradient (Gradient) -- gradient factor object * ox_tox (OxTox) -- OxTox object * metadata (str) -- Stores infos about where the model was created * units (str) -- only 'metric' allowed * COMPS (int) -- static info : number of compartments * MODEL_VALIDATION_SUCCESS (int) -- static const for validation success * MODEL_VALIDATION_FAILURE (int) -- static const for validation failure """ COMPS = 16 #TODO: SUPPRIMER CES DEUX ELEMENTS ET REMPLACER PAR DES RAISE MODEL_VALIDATION_SUCCESS = 1 MODEL_VALIDATION_FAILURE = 0 def __init__(self): """Constructor for model class *Keyword arguments:* <none> *Returns:* <nothing> *Raise:* <nothing> """ #initiate class logger self.logger = logging.getLogger( "dipplanner.model.buhlmann.model.Model") self.logger.debug("creating an instance of Model") self.units = 'metric' self.tissues = [] self.ox_tox = OxTox() self.gradient = None self.init_gradient() for _ in range(0, self.COMPS): self.tissues.append(Compartment()) self.set_time_constants() for comp in self.tissues: comp.set_pp(0.0, 0.79 * (settings.AMBIANT_PRESSURE_SURFACE - tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP))) #TODO: Check above : 0.79 or settings.DEFAULT_AIR_PPN2 (tdb) ? self.metadata = "(none)" def __deepcopy__(self, memo): """deepcopy method will be called by copy.deepcopy Used for "cloning" the object into another new object. *Keyword Arguments:* :memo: -- not used here *Returns:* Model -- Model object copy of itself *Raise:* <nothing> """ newobj = Model() newobj.units = self.units newobj.ox_tox = copy.deepcopy(self.ox_tox) newobj.gradient = copy.deepcopy(self.gradient) newobj.metadata = self.metadata for i in range(0, len(self.tissues)): newobj.tissues[i] = copy.deepcopy(self.tissues[i]) return newobj def __repr__(self): """Returns a string representing the model *Keyword Arguments:* <none> *Returns:* str -- string representation of the model *Raise:* <nothing> """ model_string = "" # "Compartment pressures:\n" for comp_number in range(0, self.COMPS): model_string += "C:%s He:%s N2:%s gf:%s \ mv_at:%s max_amb:%s MV:%s\n" % \ (comp_number, self.tissues[comp_number].pp_he, self.tissues[comp_number].pp_n2, self.gradient.gf, self.tissues[comp_number].get_m_value_at( settings.AMBIANT_PRESSURE_SURFACE), (self.tissues[comp_number].get_max_amb( self.gradient.gf)) * 1, self.tissues[comp_number].get_mv( settings.AMBIANT_PRESSURE_SURFACE)) #model_string += "Ceiling: %s\n" % self.ceiling() #model_string += "Max surface M-Value: %s\n" % self.m_value(0.0) #model_string += "OTUs accumulated: %s" % self.ox_tox.otu return model_string def __str__(self): """Return a human readable name of the segment *Keyword Arguments:* <none> *Returns:* str -- string representation of the model *Raise:* <nothing> """ return self.__repr__() def __unicode__(self): """Return a human readable name of the segment in unicode *Keyword Arguments:* <none> *Returns:* ustr -- unicode string representation of the model *Raise:* <nothing> """ return u"%s" % self.__repr__() def init_gradient(self): """Initialise the gradient attribute uses the default settings parameters for gf_low and high *Keyword arguments:* <none> *Returns:* <nothing> *Raise:* <nothing> """ self.gradient = Gradient(settings.GF_LOW, settings.GF_HIGH) def set_time_constants(self, deco_model=settings.DECO_MODEL): """Initialize time constants in buhlmann tissue list Only for metric values *Keyword arguments:* :deco_model: (str) -- "ZHL16b" or "ZHL16c" *Returns:* <nothing> *Raise:* <nothing> """ # note: comparing with buhlmann original (1990) ZH-L16 a coeficient, # there is here a x10 factor for a coeficient #h_he, h_n2, a_he, b_he, a_n2, b_n2 if deco_model == "ZHL16c": self.logger.info("model used: Buhlmann ZHL16c") self.tissues[0].set_compartment_time_constants( 1.88, 5.0, 16.189, 0.4770, 11.696, 0.5578) self.tissues[1].set_compartment_time_constants( 3.02, 8.0, 13.83, 0.5747, 10.0, 0.6514) self.tissues[2].set_compartment_time_constants( 4.72, 12.5, 11.919, 0.6527, 8.618, 0.7222) self.tissues[3].set_compartment_time_constants( 6.99, 18.5, 10.458, 0.7223, 7.562, 0.7825) self.tissues[4].set_compartment_time_constants( 10.21, 27.0, 9.220, 0.7582, 6.667, 0.8126) self.tissues[5].set_compartment_time_constants( 14.48, 38.3, 8.205, 0.7957, 5.60, 0.8434) self.tissues[6].set_compartment_time_constants( 20.53, 54.3, 7.305, 0.8279, 4.947, 0.8693) self.tissues[7].set_compartment_time_constants( 29.11, 77.0, 6.502, 0.8553, 4.5, 0.8910) self.tissues[8].set_compartment_time_constants( 41.20, 109.0, 5.950, 0.8757, 4.187, 0.9092) self.tissues[9].set_compartment_time_constants( 55.19, 146.0, 5.545, 0.8903, 3.798, 0.9222) self.tissues[10].set_compartment_time_constants( 70.69, 187.0, 5.333, 0.8997, 3.497, 0.9319) self.tissues[11].set_compartment_time_constants( 90.34, 239.0, 5.189, 0.9073, 3.223, 0.9403) self.tissues[12].set_compartment_time_constants( 115.29, 305.0, 5.181, 0.9122, 2.850, 0.9477) self.tissues[13].set_compartment_time_constants( 147.42, 390.0, 5.176, 0.9171, 2.737, 0.9544) self.tissues[14].set_compartment_time_constants( 188.24, 498.0, 5.172, 0.9217, 2.523, 0.9602) self.tissues[15].set_compartment_time_constants( 240.03, 635.0, 5.119, 0.9267, 2.327, 0.9653) elif deco_model == "ZHL16b": self.logger.info("model used: Buhlmann ZHL16b") self.tissues[0].set_compartment_time_constants( 1.88, 5.0, 16.189, 0.4770, 11.696, 0.5578) self.tissues[1].set_compartment_time_constants( 3.02, 8.0, 13.83, 0.5747, 10.0, 0.6514) self.tissues[2].set_compartment_time_constants( 4.72, 12.5, 11.919, 0.6527, 8.618, 0.7222) self.tissues[3].set_compartment_time_constants( 6.99, 18.5, 10.458, 0.7223, 7.562, 0.7825) self.tissues[4].set_compartment_time_constants( 10.21, 27.0, 9.220, 0.7582, 6.667, 0.8126) self.tissues[5].set_compartment_time_constants( 14.48, 38.3, 8.205, 0.7957, 5.933, 0.8434) self.tissues[6].set_compartment_time_constants( 20.53, 54.3, 7.305, 0.8279, 5.282, 0.8693) self.tissues[7].set_compartment_time_constants( 29.11, 77.0, 6.502, 0.8553, 4.71, 0.8910) self.tissues[8].set_compartment_time_constants( 41.20, 109.0, 5.950, 0.8757, 4.187, 0.9092) self.tissues[9].set_compartment_time_constants( 55.19, 146.0, 5.545, 0.8903, 3.798, 0.9222) self.tissues[10].set_compartment_time_constants( 70.69, 187.0, 5.333, 0.8997, 3.497, 0.9319) self.tissues[11].set_compartment_time_constants( 90.34, 239.0, 5.189, 0.9073, 3.223, 0.9403) self.tissues[12].set_compartment_time_constants( 115.29, 305.0, 5.181, 0.9122, 2.971, 0.9477) self.tissues[13].set_compartment_time_constants( 147.42, 390.0, 5.176, 0.9171, 2.737, 0.9544) self.tissues[14].set_compartment_time_constants( 188.24, 498.0, 5.172, 0.9217, 2.523, 0.9602) self.tissues[15].set_compartment_time_constants( 240.03, 635.0, 5.119, 0.9267, 2.327, 0.9653) def validate_model(self): """Validate model - checks over the model and looks for corruption This is needed to check a model that has been loaded from XML Resets time constants *Keyword arguments:* <none> *Returns:* self.MODEL_VALIDATION_SUCCESS -- if OK self.MODEL_VALIDATION_FAILURE -- if not OK *Raise:* <nothing> """ time_constant_zero = False # need for resetting time constants for comp in self.tissues: if comp.pp_n2 <= 0.0: return self.MODEL_VALIDATION_FAILURE if comp.pp_he < 0.0: return self.MODEL_VALIDATION_FAILURE if comp.k_he == 0.0 or \ comp.k_n2 == 0.0 or \ comp.a_he == 0.0 or \ comp.b_he == 0.0 or \ comp.a_n2 == 0.0 or \ comp.b_n2 == 0.0: time_constant_zero = True if time_constant_zero: self.set_time_constants() return self.MODEL_VALIDATION_SUCCESS def control_compartment(self): """Determine the controlling compartment at ceiling (1-16) *Keyword arguments:* <none> *Returns:* integer -- reference number of the controlling compartment (between 1 to 16) *Raise:* <nothing> """ control_compartment_number = 0 max_pressure = 0.0 for comp_number in range(0, self.COMPS): pressure = self.tissues[comp_number].get_max_amb( self.gradient.gf) - settings.AMBIANT_PRESSURE_SURFACE #self.logger.debug("pressure:%s" % pressure) if pressure > max_pressure: control_compartment_number = comp_number max_pressure = pressure return control_compartment_number + 1 def ceiling(self): """Determine the current ceiling depth *Keyword arguments:* <none> *Returns:* float -- ceiling depth in meter *Raise:* <nothing> """ pressure = 0.0 for comp in self.tissues: #Get compartment tolerated ambient pressure and convert from # absolute pressure to depth comp_pressure = comp.get_max_amb( self.gradient.gf) - settings.AMBIANT_PRESSURE_SURFACE if comp_pressure > pressure: pressure = comp_pressure return tools.pressure_to_depth(pressure) def ceiling_in_pabs(self): """Determine the current ceiling *Keyword arguments:* <none> *Returns:* float -- ceiling in bar (absolute pressure) *Raise:* <nothing> """ pressure = 0.0 for comp in self.tissues: #Get compartment tolerated ambient pressure and convert from #absolute pressure to depth comp_pressure = comp.get_max_amb(self.gradient.gf) if comp_pressure > pressure: pressure = comp_pressure return pressure def m_value(self, pressure): """Determine the maximum M-Value for a given depth (pressure) *Keyword arguments:* :pressure: (float) -- in bar *Returns* float -- max M-Value *Raise:* <nothing> """ p_absolute = pressure + settings.AMBIANT_PRESSURE_SURFACE compartment_mv = 0.0 max_mv = 0.0 for comp in self.tissues: compartment_mv = comp.get_mv(p_absolute) if compartment_mv > max_mv: max_mv = compartment_mv #self.logger.debug("max mv : %s" % max_mv) return max_mv def const_depth(self, pressure, seg_time, f_he, f_n2, pp_o2): """Constant depth profile. Calls Compartment.constDepth for each compartment to update the model. *Keyword arguments:* :pressure: (float)-- pressure of this depth of segment in bar :seg_time: (float) -- Time of segment in seconds :f_he: (float) -- fraction of inert gas Helium in inspired gas mix :f_n2: (float) -- fraction of inert gas Nitrogen in inspired gas mix :pp_o2: (float) -- For CCR mode, partial pressure of oxygen in bar. If == 0.0, then open circuit *Returns:* <nothing> *Raise:* ModelStateException """ ambiant_pressure = pressure + settings.AMBIANT_PRESSURE_SURFACE if pp_o2 > 0.0: # CCR mode #Determine pInert by subtracting absolute oxygen pressure and pH2O #Note that if f_he and f_n2 == 0.0 then need to force pp's to zero if f_he + f_n2 > 0.0: p_inert = ambiant_pressure - pp_o2 - \ tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP) else: p_inert = 0.0 #Verify that pInert is positive. If the setpoint is close to or # less than the depth then there is no inert gas. if p_inert > 0.0: pp_he_inspired = (p_inert * f_he) / (f_he + f_n2) pp_n2_inspired = (p_inert * f_n2) / (f_he + f_n2) else: pp_he_inspired = 0.0 pp_n2_inspired = 0.0 # update OxTox object pp_o2_inspired = pp_o2 #Check that ppO2Inspired is not greater than the depth. #This occurs in shallow deco when the setpoint specified is > depth if pp_o2_inspired <= ambiant_pressure and p_inert > 0.0: # pp_o2 is the setpoint self.ox_tox.add_o2(seg_time, pp_o2) else: # pp_o2 is equal to the depth, also true is there is # no inert gaz self.ox_tox.add_o2(seg_time, ambiant_pressure - tools.calculate_pp_h2o_surf( settings.SURFACE_TEMP)) else: # OC mode pp_he_inspired = (ambiant_pressure - tools.calculate_pp_h2o_surf( settings.SURFACE_TEMP)) * f_he pp_n2_inspired = (ambiant_pressure - tools.calculate_pp_h2o_surf( settings.SURFACE_TEMP)) * f_n2 # update ox_tox if pressure == 0.0: # surface self.ox_tox.remove_o2(seg_time) else: self.ox_tox.add_o2( seg_time, (ambiant_pressure - tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP)) * (1.0 - f_he - f_n2)) if seg_time > 0: for comp in self.tissues: comp.const_depth(pp_he_inspired, pp_n2_inspired, seg_time) def asc_desc(self, start, finish, rate, f_he, f_n2, pp_o2): """Ascend/Descend profile. Calls Compartment.asc_desc to update compartments *Keyword arguments:* :start: (float) -- start pressure of this segment in bar (WARNING: not meter ! it's a pressure) :finish: (float) -- finish pressure of this segment in bar (WARNING: not meter ! it's a pressure) :rate: (float) -- rate of ascent or descent in m/s :f_he: (float) -- Fraction of inert gas Helium in inspired gas mix :f_n2: (float) -- Fraction of inert gas Nitrogen in inspired gas mix :pp_o2: (float) -- For CCR mode, partial pressure of oxygen in bar. If == 0.0, then open circuit *Returns:* <nothing> *Raise:* ModelStateException """ # rem: here we do not bother of PP_H2O like in constant_depth : WHY ? start_ambiant_pressure = start + settings.AMBIANT_PRESSURE_SURFACE finish_ambiant_pressure = finish + settings.AMBIANT_PRESSURE_SURFACE seg_time = abs(float(finish) - float(start)) / rate if pp_o2 > 0.0: # CCR mode # Calculate inert gas partial pressure == pAmb - pO2 - pH2O p_inert_start = start_ambiant_pressure - pp_o2 - \ tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP) p_inert_finish = finish_ambiant_pressure - pp_o2 - \ tools.calculate_pp_h2o_surf(settings.SURFACE_TEMP) # Check that it doesn't go less than zero. # Could be due to shallow deco or starting on high setpoint if p_inert_start < 0.0: p_inert_start = 0.0 if p_inert_finish < 0.0: p_inert_finish = 0.0 # Separate into He and N2 components, checking that we are not # on pure O2 (or we get an arithmetic error) if f_he + f_n2 > 0.0: pp_he_inspired = (p_inert_start * f_he) / (f_he + f_n2) pp_n2_inspired = (p_inert_start * f_n2) / (f_he + f_n2) # calculate rate of change of each inert gas rate_he = ((p_inert_finish * f_he) / (f_he + f_n2) - pp_he_inspired) / (seg_time) rate_n2 = ((p_inert_finish * f_n2) / (f_he + f_n2) - pp_n2_inspired) / (seg_time) else: pp_he_inspired = 0.0 pp_n2_inspired = 0.0 rate_he = 0.0 rate_n2 = 0.0 # update ox_tox, constant pp_o2 # TODO - what if depth is less than pO2 in msw ? self.ox_tox.add_o2(seg_time, pp_o2) else: # OC mode # calculate He and N2 components pp_he_inspired = (start_ambiant_pressure - tools.calculate_pp_h2o_surf( settings.SURFACE_TEMP)) * f_he pp_n2_inspired = (start_ambiant_pressure - tools.calculate_pp_h2o_surf( settings.SURFACE_TEMP)) * f_n2 rate_he = rate * f_he rate_n2 = rate * f_n2 # update ox_tox, use average pp_o2 pp_o2_inspired_avg = ((start_ambiant_pressure - finish_ambiant_pressure) / 2 + finish_ambiant_pressure - tools.calculate_pp_h2o_surf( settings.SURFACE_TEMP)) \ * (1.0 - f_he - f_n2) self.ox_tox.add_o2(seg_time, pp_o2_inspired_avg) for comp in self.tissues: comp.asc_desc(pp_he_inspired, pp_n2_inspired, rate_he, rate_n2, seg_time)