<commit_before># ===============================================================================
# Copyright 2013 <NAME>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===============================================================================

# ============= enthought library imports =======================
from collections import namedtuple
from math import ceil
from operator import attrgetter

import six
from numpy import Inf
from pyface.message_dialog import information
from pyface.qt import QtCore
from traits.api import Event, Dict, List, Str
from traits.has_traits import HasTraits
from traitsui.handler import Handler
from uncertainties import ufloat, std_dev, nominal_value

from pychron.core.helpers.formatting import format_percent_error, floatfmt
from pychron.core.helpers.isotope_utils import sort_isotopes
from pychron.core.helpers.logger_setup import new_logger
from pychron.envisage.view_util import open_view
from pychron.experiment.utilities.identifier import make_runid, make_aliquot_step
from pychron.graph.stacked_regression_graph import ColumnStackedRegressionGraph, StackedRegressionGraph
from pychron.processing.arar_age import ArArAge
from pychron.processing.arar_constants import ArArConstants
from pychron.processing.isotope import Isotope
from pychron.pychron_constants import PLUSMINUS, NULL_STR, AR_AR

Fit = namedtuple('Fit', 'fit '
                        'filter_outliers filter_outlier_iterations filter_outlier_std_devs '
                        'error_type include_baseline_error, time_zero_offset')

logger = new_logger('Analysis')


def min_max(a, b, vs):
    return min(a, vs.min()), max(b, vs.max())


OX = 50
OY = 50
XOFFSET = 25
YOFFSET = 25
WINDOW_CNT = 0


class CloseHandler(Handler):
    def closed(self, info, is_ok):
        global WINDOW_CNT
        WINDOW_CNT -= 1
        WINDOW_CNT = max(0, WINDOW_CNT)

    def init(self, info):
        global WINDOW_CNT
        WINDOW_CNT += 1
        info.ui.control.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)


def show_evolutions_factory(record_id, isotopes, show_evo=True, show_equilibration=False, show_baseline=False,
                            show_statistics=False, ncols=1, scale_to_equilibration=False):
    if WINDOW_CNT > 20:
        information(None, 'You have too many Isotope Evolution windows open. Close some before proceeding')
        return

    if not show_evo:
        xmi = Inf
        xma = -Inf
    else:
        xmi, xma = 0, -Inf

    if ncols > 1:
        isotopes = sort_isotopes(isotopes, reverse=True, key=attrgetter('name'))

        def reorder(l, n):
            l = [l[i:i + n] for i in range(0, len(l), n)]
            nl = []
            for ri in range(len(l[0])):
                for col in l:
                    try:
                        nl.append(col[ri])
                    except IndexError:
                        pass
            return nl

        nrows = ceil(len(isotopes) / ncols)
        isotopes = reorder(isotopes, nrows)
        g = ColumnStackedRegressionGraph(resizable=True, ncols=ncols, nrows=nrows,
                                         container_dict={'padding_top': 40,
                                                         'spacing': (0, 15),
                                                         'padding_bottom': 40})
    else:
        isotopes = sort_isotopes(isotopes, reverse=False, key=attrgetter('name'))
        g = StackedRegressionGraph(resizable=True, container_dict={'spacing': 15})

    # g.plotcontainer.spacing = 10
    g.window_height = min(275 * len(isotopes), 800)
    g.window_x = OX + XOFFSET * WINDOW_CNT
    g.window_y = OY + YOFFSET * WINDOW_CNT

    for i, iso in enumerate(isotopes):
        ymi, yma = Inf, -Inf

        p = g.new_plot(padding=[80, 10, 10, 40])
        g.add_limit_tool(p, 'x')
        g.add_limit_tool(p, 'y')
        g.add_axis_tool(p, p.x_axis)
        g.add_axis_tool(p, p.y_axis)
        if show_statistics:
            g.add_statistics(i)

        p.y_axis.title_spacing = 50
        if show_equilibration:
            sniff = iso.sniff
            if sniff.xs.shape[0]:
                g.new_series(sniff.offset_xs, sniff.ys,
                             type='scatter',
                             fit=None,
                             color='red')
                ymi, yma = min_max(ymi, yma, sniff.ys)
                xmi, xma = min_max(xmi, xma, sniff.offset_xs)

        if show_evo:
            if iso.fit is None:
                iso.fit = 'linear'

            g.new_series(iso.offset_xs, iso.ys,
                         fit=iso.efit,
                         truncate=iso.truncate,
                         filter_outliers_dict=iso.filter_outliers_dict,
                         color='black')

            if not scale_to_equilibration:
                ymi, yma = min_max(ymi, yma, iso.ys)
                xmi, xma = min_max(xmi, xma, iso.offset_xs)

        if show_baseline:
            baseline = iso.baseline
            g.new_series(baseline.offset_xs, baseline.ys,
                         type='scatter', fit=baseline.efit,
                         filter_outliers_dict=baseline.filter_outliers_dict,
                         color='blue')
            if not scale_to_equilibration:
                ymi, yma = min_max(ymi, yma, baseline.ys)
                xmi, xma = min_max(xmi, xma, baseline.offset_xs)

        xpad = '0.025,0.05'
        ypad = '0.05'
        if scale_to_equilibration:
            xma *= 1.1
            ypad = None
            r = (yma - ymi) / 5
            yma += r
            ymi -= r

        g.set_x_limits(min_=xmi, max_=xma, pad=xpad)
        g.set_y_limits(min_=ymi, max_=yma, pad=ypad, plotid=i)

        g.set_x_title('Time (s)', plotid=i)
        g.set_y_title('{} ({})'.format(iso.name, iso.units), plotid=i)

    g.refresh()
    g.window_title = '{} {}'.format(record_id, ','.join([i.name for i in reversed(isotopes)]))

    return g


class IdeogramPlotable(HasTraits):
    history_id = 0
    group_id = 0
    graph_id = 0
    tab_id = 0

    _label_name = None

    tag = 'ok'
    tag_note = ''
    uage = None
    temp_status = Str('ok')
    otemp_status = None
    _record_id = None
    temp_selected = False
    comment = ''
    j = None<commit_msg>improved define equilibration scaling<commit_after># ===============================================================================
# Copyright 2013 <NAME>
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# ===============================================================================

# ============= enthought library imports =======================
from collections import namedtuple
from math import ceil
from operator import attrgetter

import six
from numpy import Inf, polyfit, polyval
from pyface.message_dialog import information
from pyface.qt import QtCore
from traits.api import Event, Dict, List, Str
from traits.has_traits import HasTraits
from traitsui.handler import Handler
from uncertainties import ufloat, std_dev, nominal_value

from pychron.core.helpers.fits import convert_fit
from pychron.core.helpers.formatting import format_percent_error, floatfmt
from pychron.core.helpers.isotope_utils import sort_isotopes
from pychron.core.helpers.logger_setup import new_logger
from pychron.envisage.view_util import open_view
from pychron.experiment.utilities.identifier import make_runid, make_aliquot_step
from pychron.graph.stacked_regression_graph import ColumnStackedRegressionGraph, StackedRegressionGraph
from pychron.processing.arar_age import ArArAge
from pychron.processing.arar_constants import ArArConstants
from pychron.processing.isotope import Isotope
from pychron.pychron_constants import PLUSMINUS, NULL_STR, AR_AR

Fit = namedtuple('Fit', 'fit '
                        'filter_outliers filter_outlier_iterations filter_outlier_std_devs '
                        'error_type include_baseline_error, time_zero_offset')

logger = new_logger('Analysis')


def min_max(a, b, vs):
    return min(a, vs.min()), max(b, vs.max())


OX = 50
OY = 50
XOFFSET = 25
YOFFSET = 25
WINDOW_CNT = 0


class CloseHandler(Handler):
    def closed(self, info, is_ok):
        global WINDOW_CNT
        WINDOW_CNT -= 1
        WINDOW_CNT = max(0, WINDOW_CNT)

    def init(self, info):
        global WINDOW_CNT
        WINDOW_CNT += 1
        info.ui.control.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)


def show_evolutions_factory(record_id, isotopes, show_evo=True, show_equilibration=False, show_baseline=False,
                            show_statistics=False, ncols=1, scale_to_equilibration=False):
    if WINDOW_CNT > 20:
        information(None, 'You have too many Isotope Evolution windows open. Close some before proceeding')
        return

    if not show_evo:
        xmi = Inf
        xma = -Inf
    else:
        xmi, xma = 0, -Inf

    if ncols > 1:
        isotopes = sort_isotopes(isotopes, reverse=True, key=attrgetter('name'))

        def reorder(l, n):
            l = [l[i:i + n] for i in range(0, len(l), n)]
            nl = []
            for ri in range(len(l[0])):
                for col in l:
                    try:
                        nl.append(col[ri])
                    except IndexError:
                        pass
            return nl

        nrows = ceil(len(isotopes) / ncols)
        isotopes = reorder(isotopes, nrows)
        g = ColumnStackedRegressionGraph(resizable=True, ncols=ncols, nrows=nrows,
                                         container_dict={'padding_top': 40,
                                                         'spacing': (0, 15),
                                                         'padding_bottom': 40})
    else:
        isotopes = sort_isotopes(isotopes, reverse=False, key=attrgetter('name'))
        g = StackedRegressionGraph(resizable=True, container_dict={'spacing': 15})

    # g.plotcontainer.spacing = 10
    g.window_height = min(275 * len(isotopes), 800)
    g.window_x = OX + XOFFSET * WINDOW_CNT
    g.window_y = OY + YOFFSET * WINDOW_CNT

    for i, iso in enumerate(isotopes):
        ymi, yma = Inf, -Inf

        p = g.new_plot(padding=[80, 10, 10, 40])
        g.add_limit_tool(p, 'x')
        g.add_limit_tool(p, 'y')
        g.add_axis_tool(p, p.x_axis)
        g.add_axis_tool(p, p.y_axis)
        if show_statistics:
            g.add_statistics(i)

        p.y_axis.title_spacing = 50
        if show_equilibration:
            sniff = iso.sniff
            if sniff.xs.shape[0]:
                g.new_series(sniff.offset_xs, sniff.ys,
                             type='scatter',
                             fit=None,
                             color='red')
                ymi, yma = min_max(ymi, yma, sniff.ys)
                xmi, xma = min_max(xmi, xma, sniff.offset_xs)

        if show_evo:
            if iso.fit is None:
                iso.fit = 'linear'

            g.new_series(iso.offset_xs, iso.ys,
                         fit=iso.efit,
                         truncate=iso.truncate,
                         filter_outliers_dict=iso.filter_outliers_dict,
                         color='black')

            if not scale_to_equilibration:
                ymi, yma = min_max(ymi, yma, iso.ys)
                xmi, xma = min_max(xmi, xma, iso.offset_xs)

        if show_baseline:
            baseline = iso.baseline
            g.new_series(baseline.offset_xs, baseline.ys,
                         type='scatter', fit=baseline.efit,
                         filter_outliers_dict=baseline.filter_outliers_dict,
                         color='blue')
            if not scale_to_equilibration:
                ymi, yma = min_max(ymi, yma, baseline.ys)
                xmi, xma = min_max(xmi, xma, baseline.offset_xs)

        xpad = '0.025,0.05'
        ypad = '0.05'
        if scale_to_equilibration:
            xma *= 1.1
            ypad = None
            r = (yma - ymi) / 5
            ymi = yma - r

            fit = iso.fit
            if fit != 'average':
                fit, _ = convert_fit(iso.fit)
                fy = polyval(polyfit(iso.offset_xs, iso.ys, fit), 0)
                if ymi > fy:
                    ymi = fy - r

                fy = polyval(polyfit(iso.offset_xs, iso.ys, fit), xma)
                if fy > yma:
                    yma = fy
                elif fy < ymi:
                    ymi = fy - r

            yma += r

        g.set_x_limits(min_=xmi, max_=xma, pad=xpad)
        g.set_y_limits(min_=ymi, max_=yma, pad=ypad, plotid=i)

        g.set_x_title('Time (s)', plotid=i)
        g.set_y_title('{} ({})'.format(iso.name, iso.units), plotid=i)

    g.refresh()
    g.window_title = '{} {}'.format(record_id, ','.join([i.name for i in reversed(isotopes)]))

    return g


class IdeogramPlotable(HasTraits):
    history_id = 0
    group_id = 0
    graph_id = 0
    tab_id = 0

    _label_name = None

    tag = 'ok'
    tag_note = ''
    uage = None
    temp_status = Str('ok')
    otemp_status = None
    _record_id = None
    temp_selected = False
    comment = ''
    j = None
{'fraction_non_alphanumeric': 0.11652981078186363, 'fraction_numerical': 0.01035344519814352, 'mean_word_length': 2.141767608793181, 'pattern_counts': {'":': 0, '<': 6, '<?xml version=': 0, '>': 11, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 12, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '7136213', 'n_tokens_mistral': 4584, 'n_tokens_neox': 4193, 'n_words': 1061}