Exemple #1
0
    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()
Exemple #3
0
    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)"
Exemple #4
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 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()
Exemple #6
0
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)
Exemple #7
0
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)
Exemple #10
0
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)