import collections
import itertools
import logging
from typing import IO, Any, Dict, List, Tuple

from haoda import ir, util
from haoda.ir import visitor
from soda import core, dataflow

_logger = logging.getLogger().getChild(__name__)

SUPPORTED_INTERFACES = 'm_axi', 'axis'

DATA_TYPE_FMT = dict(
    m_axi='Data<{0.c_type}>',
    axis='ap_axiu<{0.width_in_bits}, 0, 0, 0>',
)

def _check_interface(interface: str) -> None:
  if interface not in SUPPORTED_INTERFACES:
    raise NotImplementedError(f'interface "{interface}" is not implemented')

def _print_interface(
    printer: util.CppPrinter,
    stencil: core.Stencil,
    inputs: List[Tuple[str, ir.Type, int]],
    outputs: List[Tuple[str, ir.Type, int]],
    super_source: dataflow.SuperSourceNode,
    interface: str,
) -> None:
  """Prints the top-level module for the given arguments.

  Prints the top-level interfaces and sub-module instances with proper interface
  pragmas, hls::stream declarations and references, and module function calls.
  Currently only streaming applications are supported.

  Args:
    printer: CppPrinter to which the code is emitted.
    stencil: core.Stencil to print.
    inputs: List of (name, ir.Type, bank) tuples, specifying the input
        interfaces.
    outputs: List of (name, ir.Type, bank) tuples, specifying the output
        interfaces.
    super_source: SuperSourceNode of a DAG of HAODA nodes.
  """
  get_bundle_name = util.get_bundle_name
  get_port_name = util.get_port_name
  get_port_buf_name = util.get_port_buf_name

  if interface in {'m_axi', 'axis'}:
    printer.println('extern "C" {\n')

  params: List[str] = []
  if interface == 'm_axi':
    params.extend(f'ap_uint<{stencil.burst_width}>* {get_port_name(name, bank)}'
                  for name, _, bank in outputs + inputs)
    params.append('uint64_t coalesced_data_num')
  elif interface == 'axis':
    params.extend(f'hls::stream<ap_axiu<{stencil.burst_width}, 0, 0, 0>>&'
                  f' {get_port_name(name, bank)}'
                  for name, haoda_type, bank in outputs + inputs)

  printer.print_func(f'void {stencil.kernel_name}', params, align=0)

  # print function body
  printer.do_scope()

  if interface == 'm_axi':
    printer.printlns(
        *(f'#pragma HLS interface m_axi port = {get_port_name(name, bank)} '
          f'offset = slave bundle = {get_bundle_name(name, bank)}'
          for name, _, bank in outputs + inputs),
        *(f'#pragma HLS interface s_axilite port = {get_port_name(name, bank)} '
          'bundle = control' for name, _, bank in outputs + inputs),
        '#pragma HLS interface s_axilite port = coalesced_data_num '
        'bundle = control',
        '#pragma HLS interface s_axilite port = return bundle = control',
        '',
    )
  elif interface == 'axis':
    printer.printlns(
        *(f'#pragma HLS interface axis port = {get_port_name(name, bank)}'
          for name, _, bank in outputs + inputs),
        '#pragma HLS interface ap_ctrl_none port = return',
    )

  # port buf declarations
  if interface == 'm_axi':
    printer.printlns(
        itertools.chain.from_iterable((
            f'hls::stream<Data<ap_uint<{stencil.burst_width}>>>'
            f' {get_port_buf_name(name, bank)}'
            f'("{get_port_buf_name(name, bank)}");',
            '#pragma HLS stream'
            f' variable = {get_port_buf_name(name, bank)} depth = 32',
            f'#pragma HLS data_pack variable = {get_port_buf_name(name, bank)}',
        ) for name, haoda_type, bank in inputs + outputs))
  printer.println()

  # internal fifos
  if interface in {'m_axi', 'axis'}:
    printer.printlns(
        itertools.chain.from_iterable((
            f'hls::stream<{DATA_TYPE_FMT[interface].format(fifo)}>'
            f' {fifo.c_expr}("{fifo.c_expr}");',
            '#pragma HLS stream'
            f' variable = {fifo.c_expr} depth = {fifo.depth + 3}',
            f'#pragma HLS data_pack variable = {fifo.c_expr}' if interface ==
            'm_axi' else '',
        ) for node in super_source.tpo_valid_node_gen() for fifo in node.fifos))
  printer.println()

  # start of dataflow region
  if interface in {'m_axi', 'axis'}:
    printer.println('#pragma HLS dataflow', 0)

  # load modules
  if interface == 'm_axi':
    printer.printlns(f'BurstRead({get_port_buf_name(name, bank)},'
                     f' {get_port_name(name, bank)}, coalesced_data_num);'
                     for name, _, bank in inputs)

  # SODA modules
  for node in super_source.tpo_valid_node_gen():
    module_trait_id = super_source.module_table[node][1]
    _print_module_func_call(printer, node, module_trait_id, interface)

  # store modules
  if interface == 'm_axi':
    printer.printlns(f'BurstWrite({get_port_name(name, bank)},'
                     f' {get_port_buf_name(name, bank)}, coalesced_data_num);'
                     for name, _, bank in outputs)

  # end of dataflow region

  printer.un_scope()
  if interface in {'m_axi', 'axis'}:
    printer.printlns('', '}  // extern "C"')

def print_header(
    printer: util.CppPrinter,
    interface: str = SUPPORTED_INTERFACES[0],
) -> None:
  third_party_headers = ['ap_int']
  if interface == 'm_axi':
    third_party_headers.append('hls_stream')
  elif interface == 'axis':
    third_party_headers += 'ap_axi_sdata', 'hls_stream'

  printer.printlns(
      *map('#include <c{}>'.format, [
          'float',
          'math',
          'stdbool',
          'stddef',
          'stdint',
          'stdio',
          'string',
      ]),
      '',
      *(map('#include <{}>'.format, ['algorithm'])),
      '',
      *(map('#include <{}.h>'.format, third_party_headers)),
      '',
  )

def _print_burst_read_m_axi(printer):
  printer.println('''template <typename T>
void BurstRead(hls::stream<Data<T>>& to, T* from, uint64_t data_num) {
load:
  for (uint64_t i = 0; i < data_num;) {
#pragma HLS pipeline II = 1
    const uint64_t next_i = i + 1;
    WriteData(to, from[i], next_i < data_num);
    i = next_i;
  }
}
''')

def _print_burst_write_m_axi(printer):
  printer.println('''template <typename T>
void BurstWrite(T* to, hls::stream<Data<T>>& from, uint64_t data_num) {
store:
  for (uint64_t i = 0; i < data_num; ++i) {
#pragma HLS pipeline II = 1
    T buf;
    ReadData(buf, from);
    to[i] = buf;
  }
}
''')

def print_code(
    stencil: core.Stencil,
    output_file: IO[str],
    interface: str = SUPPORTED_INTERFACES[0],
):
  _check_interface(interface)

  _logger.info('generate kernel code as %s' % output_file.name)
  printer = util.CppPrinter(output_file)

  print_header(printer, interface)

  if interface in {'m_axi', 'axis'}:
    printer.printlns(
        '#ifdef __SYNTHESIS__',
        '#warning this file should be used for simulation only',
        '#warning synthesis result may be sub-optimal',
        '#endif  // __SYNTHESIS__',
        '',
    )

  printer.printlns(
      '// this file can be generated from the following SODA DSL',
      f'/*\n{stencil}\n*/',
      '',
      '// stencil window size:'
      f' {tuple(core.get_stencil_dim(stencil.stencil_window))}',
      f'// stencil distace: {stencil.stencil_distance}',
      '// data layout is documented at',
      '// https://github.com/Blaok/soda/blob/master/docs/data-layout.md',
      '',
  )

  if interface in {'m_axi', 'axis'}:
    _print_reinterpret(printer)

  if interface == 'm_axi':
    _print_data_struct(printer)
    _print_read_data_m_axi(printer)
    _print_write_data_m_axi(printer)
    _print_burst_read_m_axi(printer)
    _print_burst_write_m_axi(printer)
  elif interface == 'axis':
    _print_read_data_axis(printer)
    _print_write_data_axis(printer)

  for module_trait_id, module_trait in enumerate(stencil.module_traits):
    print_module_definition(
        printer,
        module_trait,
        module_trait_id,
        stencil.burst_width,
        interface,
    )

  outputs = []
  inputs = []
  for stmt in stencil.output_stmts:
    for bank in stmt.dram:
      outputs.append((stmt.name, stmt.haoda_type, bank))
  for stmt in stencil.input_stmts:
    for bank in stmt.dram:
      inputs.append((stmt.name, stmt.haoda_type, bank))
  for stmt in stencil.param_stmts:
    inputs.append(('var_%s' % stmt.name, stmt.type, 0))
  _print_interface(printer, stencil, inputs, outputs,
                   stencil.dataflow_super_source, interface)

def _print_module_func_call(printer: util.CppPrinter, node: ir.Module,
                            module_trait_id: int, interface: str) -> None:
  func_name = util.get_func_name(module_trait_id)

  if interface == 'm_axi':
    get_port_name = util.get_port_buf_name
  elif interface == 'axis':
    get_port_name = util.get_port_name

  dram_reads = tuple('  /* input*/ ' + get_port_name(dram_ref.var, bank)
                     for dram_ref, bank in node.dram_reads)
  dram_writes = tuple('  /*output*/ ' + get_port_name(dram_ref.var, bank)
                      for dram_ref, bank in node.dram_writes)
  output_fifos = tuple('  /*output*/ ' + _ for _ in node.output_fifos)
  input_fifos = tuple('  /* input*/ ' + _ for _ in node.input_fifos)
  params = dram_writes + output_fifos + input_fifos + dram_reads

  if interface in {'m_axi', 'axis'}:
    printer.print_func(func_name, params, suffix=';', align=0)

def _get_delays(obj, delays):
  if isinstance(obj, ir.DelayedRef):
    delays.append(obj)
  return obj

def _mutate_dram_ref_for_writes(obj: ir.Node, kwargs: Dict[str, Any]) -> None:
  if isinstance(obj, ir.DRAMRef):
    coalescing_idx = kwargs.pop('coalescing_idx')
    unroll_factor = kwargs.pop('unroll_factor')
    interface = kwargs.pop('interface')
    num_bank_map = kwargs.pop('num_bank_map')
    type_width = obj.haoda_type.width_in_bits
    elem_idx = coalescing_idx * unroll_factor + obj.offset
    num_banks = num_bank_map[obj.var]
    bank = obj.dram[elem_idx % num_banks]
    if interface in {'m_axi', 'axis'}:
      lsb = (elem_idx // num_banks) * type_width
      msb = lsb + type_width - 1
      return ir.Var(name=f'{obj.dram_buf_name(bank)}({msb}, {lsb})', idx=())
  return obj

def _mutate_dram_ref_for_reads(obj: ir.Node, kwargs: Dict[str, Any]) -> None:
  if isinstance(obj, ir.DRAMRef):
    coalescing_idx = kwargs.pop('coalescing_idx')
    unroll_factor = kwargs.pop('unroll_factor')
    interface = kwargs.pop('interface')
    num_bank_map = kwargs.pop('num_bank_map')
    expr = kwargs.pop('expr')
    type_width = obj.haoda_type.width_in_bits
    elem_idx = coalescing_idx * unroll_factor + obj.offset
    num_banks = num_bank_map[obj.var]
    bank = expr.dram[elem_idx % num_banks]
    if interface in {'m_axi', 'axis'}:
      lsb = (elem_idx // num_banks) * type_width
      msb = lsb + type_width - 1
      return ir.Var(
          name=f'Reinterpret<{obj.c_type}>('
          f'static_cast<ap_uint<{msb - lsb + 1}>>('
          f'{obj.dram_buf_name(bank)}({msb}, {lsb})))',
          idx=(),
      )
  return obj

def _process_accesses(
    module_trait: ir.ModuleTrait,
    burst_width: int,
    interface: str,
):
  # input/output channels
  if interface in {'m_axi', 'axis'}:
    fifo_loads = [
        f'/* input*/ hls::stream<{DATA_TYPE_FMT[interface].format(x)}>&'
        f' {x.ld_name}' for x in module_trait.loads
    ]
    fifo_stores = [
        f'/*output*/ hls::stream<{DATA_TYPE_FMT[interface].format(expr)}>&'
        f' {ir.FIFORef.ST_PREFIX}{idx}'
        for idx, expr in enumerate(module_trait.exprs)
    ]

  # format strings for input/output channels for packing/unpacking modules
  if interface == 'm_axi':
    fifo_load_fmt = ("f'/* input*/ hls::stream<Data<ap_uint<{burst_width}>>>&"
                     " {x.dram_fifo_name(bank)}'")
    fifo_store_fmt = ("f'/*output*/ hls::stream<Data<ap_uint<{burst_width}>>>&"
                      " {x.dram_fifo_name(bank)}'")
  elif interface == 'axis':
    fifo_load_fmt = (
        "f'/* input*/ hls::stream<ap_axiu<{burst_width}, 0, 0, 0>>&"
        " {x.dram_fifo_name(bank)}'")
    fifo_store_fmt = (
        "f'/*output*/ hls::stream<ap_axiu<{burst_width}, 0, 0, 0>>&"
        " {x.dram_fifo_name(bank)}'")

  # dict mapping variable name to
  #   dict mapping bank tuple to
  #     dict mapping offset to ir.DRAMRef
  dram_read_map: Dict[str, Dict[Tuple[int, ...], Dict[int, ir.DRAMRef]]]
  dram_read_map = collections.defaultdict(dict)
  dram_write_map: Dict[str, Dict[Tuple[int, ...], Dict[int, ir.DRAMRef]]]
  dram_write_map = collections.defaultdict(dict)

  num_bank_map: Dict[str, int] = {}
  all_dram_reads: List[ir.DRAMRef] = []
  dram_reads: List[ir.DRAMRef] = []
  coalescing_factor = 0
  ii = 1

  exprs = [_.expr for _ in module_trait.lets]
  exprs.extend(module_trait.exprs)
  dram_read_refs: Tuple[ir.DRAMRef, ...] = visitor.get_dram_refs(exprs)
  dram_write_refs: Tuple[ir.DRAMRef, ...] = visitor.get_dram_refs(
      _.name for _ in module_trait.lets if not isinstance(_.name, str))

  # temporary dict mapping variable name to
  #   dict mapping bank tuple to
  #     list of ir.DRAMRef
  dram_map: Dict[str, Dict[Tuple[int, ...], List[ir.DRAMRef]]]
  dram_map = collections.defaultdict(lambda: collections.defaultdict(list))

  if dram_read_refs:  # this is an unpacking module
    assert not dram_write_refs, 'cannot read and write DRAM in the same module'
    for dram_ref in dram_read_refs:
      dram_map[dram_ref.var][dram_ref.dram].append(dram_ref)
    _logger.debug('dram read map: %s', dram_map)
    for var in dram_map:
      for dram in dram_map[var]:
        # number of elements per cycle
        batch_size = len(dram_map[var][dram])
        dram_read_map[var][dram] = {_.offset: _ for _ in dram_map[var][dram]}
        offset_dict = dram_read_map[var][dram]
        num_banks = len(dram)
        if var in num_bank_map:
          assert num_bank_map[var] == num_banks, 'inconsistent num banks'
        else:
          num_bank_map[var] = num_banks
        _logger.debug('dram reads: %s', offset_dict)
        assert tuple(sorted(offset_dict.keys())) == tuple(range(batch_size)), \
               'unexpected DRAM accesses pattern %s' % offset_dict
        batch_width = sum(
            _.haoda_type.width_in_bits for _ in offset_dict.values())
        if burst_width * num_banks >= batch_width:
          assert burst_width * num_banks % batch_width == 0, \
              'cannot process such a burst'
          # a single burst consumed in multiple cycles
          coalescing_factor = burst_width * num_banks // batch_width
          ii = coalescing_factor
        else:
          assert batch_width * num_banks % burst_width == 0, \
              'cannot process such a burst'
          # multiple bursts consumed in a single cycle
          # reassemble_factor = batch_width // (burst_width * num_banks)
          raise util.InternalError('cannot process such a burst yet')
      dram_reads = [next(iter(_.values())) for _ in dram_read_map[var].values()]
      all_dram_reads += dram_reads
      fifo_loads.extend(
          eval(fifo_load_fmt, dict(burst_width=burst_width), locals())
          for x in dram_reads
          for bank in x.dram)
  elif dram_write_refs:  # this is a packing module
    for dram_ref in dram_write_refs:
      dram_map[dram_ref.var][dram_ref.dram].append(dram_ref)
    _logger.debug('dram write map: %s', dram_map)
    for var in dram_map:
      for dram in dram_map[var]:
        # number of elements per cycle
        batch_size = len(dram_map[var][dram])
        dram_write_map[var][dram] = {_.offset: _ for _ in dram_map[var][dram]}
        offset_dict = dram_write_map[var][dram]
        num_banks = len(dram)
        if var in num_bank_map:
          assert num_bank_map[var] == num_banks, 'inconsistent num banks'
        else:
          num_bank_map[var] = num_banks
        _logger.debug('dram writes: %s', offset_dict)
        assert tuple(sorted(offset_dict.keys())) == tuple(range(batch_size)), \
               'unexpected DRAM accesses pattern %s' % offset_dict
        batch_width = sum(
            _.haoda_type.width_in_bits for _ in offset_dict.values())
        if burst_width * num_banks >= batch_width:
          assert burst_width * num_banks % batch_width == 0, \
              'cannot process such a burst'
          # a single burst consumed in multiple cycles
          coalescing_factor = burst_width * num_banks // batch_width
          ii = coalescing_factor
        else:
          assert batch_width * num_banks % burst_width == 0, \
              'cannot process such a burst'
          # multiple bursts consumed in a single cycle
          # reassemble_factor = batch_width // (burst_width * num_banks)
          raise util.InternalError('cannot process such a burst yet')
      dram_writes = [
          next(iter(_.values())) for _ in dram_write_map[var].values()
      ]
      fifo_stores.extend(
          eval(fifo_store_fmt, dict(burst_width=burst_width), locals())
          for x in dram_writes
          for bank in x.dram)

  return (
      fifo_loads,
      fifo_stores,
      dram_read_map,
      dram_write_map,
      num_bank_map,
      all_dram_reads,
      coalescing_factor,
      ii,
  )

def print_module_definition(
    printer: util.CppPrinter,
    module_trait: ir.ModuleTrait,
    module_trait_id: int,
    burst_width: int,
    interface: str = SUPPORTED_INTERFACES[0],
) -> None:
  func_name = util.get_func_name(module_trait_id)
  func_lower_name = util.get_module_name(module_trait_id)

  delays: ir.DelayedRef = []
  for let in module_trait.lets:
    let.visit(_get_delays, delays)
  for expr in module_trait.exprs:
    expr.visit(_get_delays, delays)
  _logger.debug('delays: %s', delays)

  (
      fifo_loads,
      fifo_stores,
      dram_read_map,
      dram_write_map,
      num_bank_map,
      dram_reads,
      coalescing_factor,
      ii,
  ) = _process_accesses(
      module_trait,
      burst_width,
      interface,
  )

  dram_rw_map = {**dram_read_map, **dram_write_map}

  # print function
  printer.print_func(f'void {func_name}', fifo_stores + fifo_loads, align=0)
  printer.do_scope(func_name)

  if interface == 'm_axi':
    printer.printlns(
        *(f'#pragma HLS data_pack variable = {dram_ref.dram_fifo_name(bank)}'
          for dram_ref, bank in module_trait.dram_writes +
          module_trait.dram_reads),
        *(f'#pragma HLS data_pack variable = {arg}'
          for arg in module_trait.output_fifos + module_trait.input_fifos),
    )

  # print inter-iteration declarations
  printer.printlns(x.c_buf_decl for x in delays)
  printer.printlns(x.c_ptr_decl for x in delays)

  # print loop
  printer.println(f'{func_lower_name}:', indent=0)
  if interface in {'m_axi', 'axis'}:
    printer.println('for (bool enable = true; enable;)')
  else:
    printer.println('for (;;)')
  printer.do_scope(f'for {func_lower_name}')
  printer.printlns(
      f'#pragma HLS pipeline II = {ii}',
      *(f'#pragma HLS dependence variable = {delay.buf_name} inter false'
        for delay in delays),
  )

  # print emptyness tests
  printer.println('if (%s)' % (' && '.join(
      f'!{fifo}.empty()' for fifo in [_.ld_name for _ in module_trait.loads] +
      [_.dram_fifo_name(bank) for _ in dram_reads for bank in _.dram])))
  printer.do_scope('if not empty')

  # print intra-iteration declarations
  printer.printlns(
      f'{fifo_in.c_type} {fifo_in.ref_name};' for fifo_in in module_trait.loads)
  if interface in {'m_axi', 'axis'}:
    printer.printlns(
        f'ap_uint<{burst_width}> {dram.dram_buf_name(bank)};'
        for var, accesses in dram_rw_map.items()
        for dram in (next(iter(_.values())) for _ in accesses.values())
        for bank in dram.dram)

  if interface in {'m_axi', 'axis'}:
    # print enable conditions
    if not dram_write_map:
      printer.printlns(f'const bool {fifo_in.ref_name}_enable = '
                       f'ReadData({fifo_in.ref_name}, {fifo_in.ld_name});'
                       for fifo_in in module_trait.loads)
    printer.printlns(
        f'const bool {x.dram_buf_name(bank)}_enable = '
        f'ReadData({x.dram_buf_name(bank)}, {x.dram_fifo_name(bank)});'
        for x in dram_reads
        for bank in x.dram)
    if not dram_write_map:
      printer.println(
          'const bool enabled = %s;' %
          ' && '.join([f'{y.ref_name}_enable' for y in module_trait.loads] + [
              f'{x.dram_buf_name(bank)}_enable' for x in dram_reads
              for bank in x.dram
          ]))
      printer.println('enable = enabled;')

  # print delays (if any)
  printer.printlns(f'const {x.c_type} {x.c_buf_load};' for x in delays)

  # mutate dram ref for writes
  if dram_write_map:
    for coalescing_idx in range(coalescing_factor):
      if interface in {'m_axi', 'axis'}:
        for fifo_in in module_trait.loads:
          if coalescing_idx == coalescing_factor - 1:
            prefix = f'const bool {fifo_in.ref_name}_enable = '
          else:
            prefix = ''
          printer.println(f'{prefix}ReadData({fifo_in.ref_name},'
                          f' {fifo_in.ld_name});')
        if coalescing_idx == coalescing_factor - 1:
          printer.printlns(
              'const bool enabled = %s;' %
              ' && '.join([f'{x.ref_name}_enable' for x in module_trait.loads] +
                          [
                              f'{x.dram_buf_name(bank)}_enable'
                              for x in dram_reads
                              for bank in x.dram
                          ]),
              'enable = enabled;',
          )
      for idx, let in enumerate(module_trait.lets):
        let = let.visit(
            _mutate_dram_ref_for_writes,
            dict(
                coalescing_idx=coalescing_idx,
                unroll_factor=len(dram_write_map[let.name.var][let.name.dram]),
                num_bank_map=num_bank_map,
                interface=interface,
            ))
        if interface in {'m_axi', 'axis'}:
          printer.println(
              f'{let.name} = '
              f'Reinterpret<ap_uint<{let.expr.haoda_type.width_in_bits}>>'
              f'({let.expr.c_expr});')
    if interface in {'m_axi', 'axis'}:
      printer.printlns(
          f'WriteData({dram.dram_fifo_name(bank)}, '
          f'{dram.dram_buf_name(bank)}, enabled);' for var in dram_write_map
          for dram in (
              next(iter(_.values())) for _ in dram_write_map[var].values())
          for bank in dram.dram)
  else:
    printer.printlns(let.c_expr for let in module_trait.lets)

  # mutate dram ref for reads
  if dram_read_map:
    for coalescing_idx in range(coalescing_factor):
      for idx, expr in enumerate(module_trait.exprs):
        c_expr = expr.visit(
            _mutate_dram_ref_for_reads,
            dict(
                coalescing_idx=coalescing_idx,
                unroll_factor=len(dram_read_map[expr.var][expr.dram]),
                num_bank_map=num_bank_map,
                interface=interface,
                expr=expr,
            )).c_expr
        if interface in {'m_axi', 'axis'}:
          if coalescing_idx < coalescing_factor - 1:
            enabled = 'true'
          else:
            enabled = 'enabled'
          printer.println(
              f'WriteData({ir.FIFORef.ST_PREFIX}{idx}, {c_expr}, {enabled});')
  else:
    if interface in {'m_axi', 'axis'}:
      printer.printlns(f'WriteData({ir.FIFORef.ST_PREFIX}{idx}, '
                       f'{expr.c_type}({expr.c_expr}), enabled);'
                       for idx, expr in enumerate(module_trait.exprs))

  for delay in delays:
    printer.printlns(
        delay.c_buf_store,
        f'{delay.ptr} = {delay.c_next_ptr_expr};',
    )

  printer.un_scope()
  printer.un_scope()
  printer.un_scope()
  printer.println()
  _logger.debug('printing: %s', module_trait)

def _print_data_struct(printer):
  printer.println('''template<typename T>
struct Data {
  T data;
  bool ctrl;
};
''')

def _print_reinterpret(printer):
  printer.println('''template<typename To, typename From>
To Reinterpret(From val) {
#pragma HLS inline
  return reinterpret_cast<To&>(val);
}
''')

def _print_read_data_m_axi(printer):
  printer.println('''template<typename T>
bool ReadData(T& data, hls::stream<Data<T>>& from) {
#pragma HLS inline
  const auto tmp = from.read();
  data = tmp.data;
  return tmp.ctrl;
}
''')

def _print_write_data_m_axi(printer):
  printer.println('''template<typename T>
void WriteData(hls::stream<Data<T>>& to, const T& data, bool ctrl) {
#pragma HLS inline
  Data<T> tmp;
  tmp.data = data;
  tmp.ctrl = ctrl;
  to.write(tmp);
}
''')

def _print_read_data_axis(printer):
  printer.println('''template<typename T, int D>
bool ReadData(T& data, hls::stream<ap_axiu<D, 0, 0, 0>>& from) {
#pragma HLS inline
  const auto tmp = from.read();
  data = Reinterpret<T>(tmp.data);
  return !tmp.last;
}
''')

def _print_write_data_axis(printer):
  printer.println('''template<typename T, int D>
void WriteData(hls::stream<ap_axiu<D, 0, 0, 0>>& to, const T& data, bool ctrl) {
#pragma HLS inline
  ap_axiu<D, 0, 0, 0> tmp = {data, 0, 0, !ctrl};
  tmp.keep.b_not();
  tmp.strb.b_not();
  to.write(tmp);
}
''')


#######################################################################
# library
#######################################################################
import seaborn as sns
import matplotlib.pyplot as plt
import numpy as np

from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
from sklearn.pipeline import make_pipeline

#######################################################################
# Polynomial basis functions
#######################################################################

from sklearn.preprocessing import PolynomialFeatures
from sklearn.linear_model import LinearRegression
x= np.array([2,3,4])
poly = PolynomialFeatures(3,include_bias=False)
# convert one-dimensional array into a three-dimensional array
poly.fit_transform(x[:, None])

from sklearn.pipeline import make_pipeline
# Make a 7th-degree polynomial model
poly_model = make_pipeline(PolynomialFeatures(7),LinearRegression())
# create training data
rng = np.random.RandomState(1)
x = 10 * rng.rand(50)
y = np.sin(x) + 0.1 * rng.randn(50)
# Fit the model
poly_model.fit(x[:,np.newaxis], y)
# create test data
xfit = np.linspace(0, 10, 1000)
yfit = poly_model.predict(xfit[:,np.newaxis])

plt.scatter(x,y)
plt.plot(xfit,yfit)
plt.show()

#######################################################################
# Gaussian basis functions
#######################################################################

#######################################################################
# PolynomialRegression
#######################################################################
# Use a pipeline to string operations together
def PolynomialRegression(degree=2, **kwargs):
    return make_pipeline(PolynomialFeatures(degree),
                         LinearRegression(**kwargs))

# generate data
def make_data(N, err=1.0, rseed=1):
    # randomly sample the data
    rng = np.random.RandomState(rseed)
    X = rng.rand(N, 1) ** 2
    y = 10 - 1. / (X.ravel() + 0.1)
    if err > 0:
        y += err * rng.randn(N)
    return X, y

# Call the function
X, y = make_data(40)

# visualize our data
plt.scatter(X.ravel(), y, color='black')
axis = plt.axis()

# generate X_test
X_test = np.linspace(-0.1, 1.1, 500)[:, None]
# Fit Polynomial Regression
for degree in [1, 3, 5]:
    y_test = PolynomialRegression(degree).fit(X,y).predict(X_test)
    plt.plot(X_test.ravel(),y_test,label='degree{0}'.format(degree))
plt.xlim(-0.1, 1.0)
plt.ylim(-2, 12)
plt.legend(loc='best');
plt.show()

# Compute training score and validation score across the range
from sklearn.model_selection import validation_curve
degree = np.arange(0,21)
train_score, val_score = validation_curve(PolynomialRegression(),X,y,'polynomialfeatures__degree',degree,cv=7)
plt.plot(degree, np.median(train_score, 1), color='blue', label='training score')
plt.plot(degree, np.median(val_score, 1), color='red', label='validation score')
plt.legend(loc='best')
plt.ylim(0, 1)
plt.xlabel('degree')
plt.ylabel('score');
plt.show()

# Compute and display the fit of degree d over the original data
plt.scatter(X.ravel(), y)
lim = plt.axis()
y_test = PolynomialRegression(3).fit(X, y).predict(X_test)
plt.plot(X_test.ravel(), y_test);
plt.axis(lim);
plt.show()

#######################################################################
# Learning Curves
#######################################################################

# Compute training score and validation score across the range
from sklearn.model_selection import validation_curve
degree = np.arange(0,21)
train_score, val_score = validation_curve(PolynomialRegression(),X,y,'polynomialfeatures__degree',degree,cv=7)
plt.plot(degree, np.median(train_score, 1), color='blue', label='training score')
plt.plot(degree, np.median(val_score, 1), color='red', label='validation score')
plt.legend(loc='best')
plt.ylim(0, 1)
plt.xlabel('degree')
plt.ylabel('score');
plt.show()

# generate a new dataset with a factor of five more points
X2, y2 = make_data(200)
plt.scatter(X2.ravel(), y2);
plt.show()

# Compute training score and validation score across the range
from sklearn.model_selection import validation_curve
degree = np.arange(21)
train_score2, val_score2 = validation_curve(PolynomialRegression(), X2, y2,'polynomialfeatures__degree', degree, cv=7)
plt.plot(degree, np.median(train_score2, 1), color='blue', label='training score')
plt.plot(degree, np.median(val_score2, 1), color='red', label='validation score')
plt.plot(degree, np.median(train_score, 1), color='blue', alpha=0.3, linestyle='dashed')
plt.plot(degree, np.median(val_score, 1), color='red', alpha=0.3, linestyle='dashed')
plt.legend(loc='lower center')
plt.ylim(0, 1)
plt.xlabel('degree')
plt.ylabel('score');
plt.show()

#######################################################################
# Validation in Practice: Grid Search
#######################################################################
# Using grid to find the optimal polynomial model
from sklearn.model_selection import GridSearchCV
# set parameters
param_grid = {'polynomialfeatures__degree': np.arange(21),
              'linearregression__fit_intercept': [True, False],
              'linearregression__normalize': [True, False]}
grid = GridSearchCV(PolynomialRegression(), param_grid, cv=7)
# Fit the model at each grid point, keeping track of the scores along the way
grid.fit(X, y);
# Ask the best parameters
grid.best_params_
# Use the best model and show the fit over the data
model = grid.best_estimator_

plt.scatter(X.ravel(), y)
lim = plt.axis()
y_test = model.fit(X, y).predict(X_test)
plt.plot(X_test.ravel(), y_test);
plt.axis(lim);
plt.show()

#######################################################################
# Basis Function Regression
#######################################################################
# transform data according to basis functions

#----------------------------------------------------------------------
# Polynomial basis functions
#----------------------------------------------------------------------
from sklearn.preprocessing import PolynomialFeatures
x = np.array([2, 3, 4])
poly = PolynomialFeatures(3, include_bias=False)
poly.fit_transform(x[:, None])

xfit = np.linspace(0, 10, 1000)

from sklearn.pipeline import make_pipeline
poly_model = make_pipeline(PolynomialFeatures(7),
                           LinearRegression())

rng = np.random.RandomState(1)
x = 10 * rng.rand(50)
y = np.sin(x) + 0.1 * rng.randn(50)

poly_model.fit(x[:, np.newaxis], y)
yfit = poly_model.predict(xfit[:, np.newaxis])

plt.scatter(x, y)
plt.plot(xfit, yfit);
plt.show()

#----------------------------------------------------------------------
# Gaussian basis functions
#----------------------------------------------------------------------
from sklearn.base import BaseEstimator, TransformerMixin

class GaussianFeatures(BaseEstimator, TransformerMixin):
    """Uniformly spaced Gaussian features for one-dimensional input"""

    def __init__(self, N, width_factor=2.0):
        self.N = N
        self.width_factor = width_factor

    @staticmethod
    def _gauss_basis(x, y, width, axis=None):
        arg = (x - y) / width
        return np.exp(-0.5 * np.sum(arg ** 2, axis))

    def fit(self, X, y=None):
        # create N centers spread along the data range
        self.centers_ = np.linspace(X.min(), X.max(), self.N)
        self.width_ = self.width_factor * (self.centers_[1] - self.centers_[0])
        return self

    def transform(self, X):
        return self._gauss_basis(X[:, :, np.newaxis], self.centers_,
                                 self.width_, axis=1)

gauss_model = make_pipeline(GaussianFeatures(20),
                            LinearRegression())
gauss_model.fit(x[:, np.newaxis], y)
yfit = gauss_model.predict(xfit[:, np.newaxis])

plt.scatter(x, y)
plt.plot(xfit, yfit)
plt.xlim(0, 10);
plt.show()

#----------------------------------------------------------------------
# Regularization
#----------------------------------------------------------------------
model = make_pipeline(GaussianFeatures(30),
                      LinearRegression())
model.fit(x[:, np.newaxis], y)

plt.scatter(x, y)
plt.plot(xfit, model.predict(xfit[:, np.newaxis]))

plt.xlim(0, 10)
plt.ylim(-1.5, 1.5);
plt.show()

def basis_plot(model, title=None):
    fig, ax = plt.subplots(2, sharex=True)
    model.fit(x[:, np.newaxis], y)
    ax[0].scatter(x, y)
    ax[0].plot(xfit, model.predict(xfit[:, np.newaxis]))
    ax[0].set(xlabel='x', ylabel='y', ylim=(-1.5, 1.5))

    if title:
        ax[0].set_title(title)

    ax[1].plot(model.steps[0][1].centers_,
               model.steps[1][1].coef_)
    ax[1].set(xlabel='basis location',
              ylabel='coefficient',
              xlim=(0, 10))

model = make_pipeline(GaussianFeatures(30), LinearRegression())
basis_plot(model)
plt.show()

#----------------------------------------------------------------------
# Ridge regression ($L_2$ Regularization)
#----------------------------------------------------------------------

from sklearn.linear_model import Ridge
model = make_pipeline(GaussianFeatures(30), Ridge(alpha=0.1))
basis_plot(model, title='Ridge Regression')
plt.show()

#----------------------------------------------------------------------
# Lasso regression ($L_1$ regularization)
#----------------------------------------------------------------------
from sklearn.linear_model import Lasso
model = make_pipeline(GaussianFeatures(30), Lasso(alpha=0.001))
basis_plot(model, title='Lasso Regression')
plt.show()

#######################################################################
# Predicting Bicycle Traffic
#######################################################################
# !curl -o FremontBridge.csv https://data.seattle.gov/api/views/65db-xm6k/rows.csv?accessType=DOWNLOAD
import pandas as pd
counts = pd.read_csv('FremontBridge.csv', index_col='Date', parse_dates=True)
weather = pd.read_csv('data/BicycleWeather.csv', index_col='DATE', parse_dates=True)

daily = counts.resample('d').sum()
daily['Total'] = daily.sum(axis=1)
daily = daily[['Total']] # remove other columns

days = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun']
for i in range(7):
    daily[days[i]] = (daily.index.dayofweek == i).astype(float)

from pandas.tseries.holiday import USFederalHolidayCalendar
cal = USFederalHolidayCalendar()
holidays = cal.holidays('2012', '2016')
daily = daily.join(pd.Series(1, index=holidays, name='holiday'))
daily['holiday'].fillna(0, inplace=True)

def hours_of_daylight(date, axis=23.44, latitude=47.61):
    """Compute the hours of daylight for the given date"""
    days = (date - pd.datetime(2000, 12, 21)).days
    m = (1. - np.tan(np.radians(latitude))
         * np.tan(np.radians(axis) * np.cos(days * 2 * np.pi / 365.25)))
    return 24. * np.degrees(np.arccos(1 - np.clip(m, 0, 2))) / 180.

daily['daylight_hrs'] = list(map(hours_of_daylight, daily.index))
daily[['daylight_hrs']].plot()
plt.ylim(8, 17)

# temperatures are in 1/10 deg C; convert to C
weather['TMIN'] /= 10
weather['TMAX'] /= 10
weather['Temp (C)'] = 0.5 * (weather['TMIN'] + weather['TMAX'])

# precip is in 1/10 mm; convert to inches
weather['PRCP'] /= 254
weather['dry day'] = (weather['PRCP'] == 0).astype(int)

daily = daily.join(weather[['PRCP', 'Temp (C)', 'dry day']])

daily['annual'] = (daily.index - daily.index[0]).days / 365.

# Drop any rows with null values
daily.dropna(axis=0, how='any', inplace=True)

column_names = ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun', 'holiday',
                'daylight_hrs', 'PRCP', 'dry day', 'Temp (C)', 'annual']
X = daily[column_names]
y = daily['Total']

model = LinearRegression(fit_intercept=False)
model.fit(X, y)
daily['predicted'] = model.predict(X)

daily[['Total', 'predicted']].plot(alpha=0.5);

params = pd.Series(model.coef_, index=X.columns)
params

from sklearn.utils import resample
np.random.seed(1)
err = np.std([model.fit(*resample(X, y)).coef_
              for i in range(1000)], 0)

print(pd.DataFrame({'effect': params.round(0),
                    'error': err.round(0)}))


# -*- codeing: UTF-8 -*-

# 迭代
# python的for循环抽象程度要高于C的for循环，因为python的for循环不仅可以用在list或tuple上，还可以作用在其他可以迭代的对象上。
# list数据是有下标的，但很多其他数据类型是没有下标的，只要是可迭代对象，无论有无下标，都可以迭代，比如dict就可以迭代

d = {'a':1, 'b':2, 'c':3}
for key in d :
  print(key)  # a c b

#之所以是 a c b ，dic的存储不是按照list的方式顺序排列，所以迭代出的结果顺序很可能不一样
# 默认情况下，dict迭代的是key，
# 如果要迭代value,可以用  for value in d.values()
# 如果要同时迭代key和value，可以用 for k,v in d.items()

for key,value in d.items():
  print(key,value)

# 字符串也是可以迭代对象，也可以用for循环
for ch in 'ABC':
  print(ch)

# 在python中，当使用for循环时，只要作用一个可迭代对象，for循环都可以正常运行，而我们不太关心该对象究竟是list还是其他数据类型

# 判断一个对象时可迭代对象： 方法是通过 collections模块的Iterable类型判断
from collections import Iterable
isinstance('abc',Iterable)  # True   判断 str 是否可迭代
isinstance([1,2,3],Iterable)  # True 判断 list 是否可迭代
isinstance(123,Iterable)  # False  判断整数是否可迭代

# 如果要对list实现 类似 java 那样的下标循环  python内置的 enumerate 函数可以把一个list变成索引-元素对
# 这样就可以在for循环中同时迭代索引和元素本身
for i,value in enumerate(['A','B','C']):
  print(i,value)
# 0 A
# 1 B
# 2 C

# 上面的循环里同时引用了两个变量，在python里很常见，如
for x,y in [(1,1),(2,4),(3,9)]:
  print(x,y)
# 1 1
# 2 4
# 3 9

# 练习
# 使用迭代 查找一个list钟最小和最大值，并返回一个tuple
def findMinAndMax(L):
  if len(L) == 0:
    return (None,None)
  min = L[0]
  max = L[0]
  for num in L:
    if num < min:
      min = num
    if num > max:
      max = num
  return (min,max)

# 测试
if findMinAndMax([]) != (None, None):
    print('测试失败!')
elif findMinAndMax([7]) != (7, 7):
    print('测试失败!')
elif findMinAndMax([7, 1]) != (1, 7):
    print('测试失败!')
elif findMinAndMax([7, 1, 3, 9, 5]) != (1, 9):
    print('测试失败!')
else:
    print('测试成功!')


import sys
from PyQt5.QtWidgets import QApplication, QWidget, QPushButton, QButtonGroup
from PyQt5.QtGui import QIcon, QPainter, QBrush, QPen
from PyQt5.QtCore import pyqtSlot, Qt
from collections.abc import Sequence
from itertools import chain, count
import numpy as np
import tree

testData = '''(  (IP-MAT
    (NP-SBJ (PRO-D Honum))
    (VBDI blöskraði)
    (PP
      (P þegar)
      (CP-ADV
        (C 0)
        (IP-SUB
          (NP-SBJ (NP (D-D þessi) (N-D niðurstaða)))
          (RDDI varð)
          (ADJP (ADJ-N ljós)))))))'''

testVal = tree.Tree([testData], 0)

class App(QWidget):

    def __init__(self, text):
        super().__init__()
        self.positions = []
        self.rightmostX = 0
        self.paintPos = []
        self.currentID = 1
        self.currentPosition = [0]
        self.buttongroup = QButtonGroup()
        self.title = 'Tree'
        self.left = 10
        self.top = 10
        self.width = 700
        self.height = 700
        self.bWidth = 80
        self.bHeight = 40
        self.margin = 20
        self.selected = []

        self.depth = self.findDepth(text)
        self.initUI(text)

    def increseID(self):
        self.currentID += 1

    #draws a arrow from parent to child
    def paintEvent(self, event):
        for i in self.paintPos:
            qp = QPainter()
            pxPos = int(i["pxPos"])
            pyPos = int(i["pyPos"])
            cxPos = int(i["cxPos"])
            cyPos = int(i["cyPos"])

            qp.begin(self)

            qp.setPen(Qt.red)

            qp.setBrush(Qt.white)
            qp.drawLine(pxPos, pyPos, cxPos, cyPos)
        qp.end()

    def initUI(self, text):
        self.setWindowTitle(self.title)
        self.setGeometry(self.left, self.top, self.width, self.height)
        self.buttongroup.buttonClicked[int].connect(self.on_button_clicked)
        if not self.buttongroup.buttons():

            buttons = self.createButtons(text)
            for b in buttons:
                button = QPushButton(b["name"], self)
                button.setGeometry(int(b["xpos"]),int(b["ypos"]),self.bWidth,self.bHeight)
                self.buttongroup.addButton(button,b["id"])

        #button1.clicked.connect(self.on_click)
        #button2.clicked.connect(self.on_click)
        self.paintEvent("test")
        #TEMP CHANGE LATER
        self.show()

    def on_button_clicked(self,ID):
        print(ID)

    def createButtons(self,text):
        buttons = []

        yPos = 10
        #below is temp, change later
        sent = text[0][1]
        depth = self.findDepth(sent)
        maxHeight = self.height // depth

        parentX = self.width/2
        parentY = self.bHeight + 40

        button = {
                    "id": self.currentID,
                    "name" : sent[0],
                    "xpos" : self.width/2,
                    "ypos" : self.bHeight + 40,
                    "position": self.currentPosition[:]
                }
        buttons.append(button)
        self.positions.append(self.currentPosition[:])

        buttons2 = self.createLayout(sent,self.width/2, parentX + (self.bWidth/2), parentY + (self.bHeight/2))
        buttons = buttons + buttons2

        for i in buttons:
            t = "print(sent"
            for j in i["position"]:
                temp = '[' + str(j) + ']'
                t += temp
            t += ')'
            exec(t)

        return buttons

    def findDepth(self, text):
        depth = lambda L: isinstance(L, list) and max(map(depth, L))+1
        return depth(text)

    def DefaultXPos(self, sentence, xPos, buttons):
        #find how many notes are in this depth excluding the head

        #start by checking if the rightmost xposition is clos

        length = len(sentence)-1
        xPositions = []
        #create list of xPositions

        if length%2 == 0:

            #find new xpos in respect to the children of previous note

            left = []
            right = []

            #left list
            for i in range(int(length/2)):
                if not left:
                    left.append(xPos-(self.bWidth + 20))
                else:
                    left.append(left[-1]-(self.bWidth + 20))

            #right list
            for i in range(int(length/2)):
                if not right:
                    right.append(xPos+(self.bWidth + 20))
                else:
                    right.append(right[-1]+(self.bWidth + 20))

            left.reverse()
            xPositions = left + right
        else:
            mid = []
            mid.append(int(xPos))
            left = []
            right = []

            #left list
            for i in range(int((length-1)/2)):
                if not left:
                    left.append(xPos-(self.bWidth + 20))
                else:
                    left.append(left[-1]-(self.bWidth + 20))

            #right list
            for i in range(int((length-1)/2)):
                if not right:
                    right.append(xPos+(self.bWidth + 20))
                else:
                    right.append(right[-1]+(self.bWidth + 20))
            left.reverse()
            xPositions = left + mid + right
        return xPositions

    def createLayout(self, sentence, xPos, parentX, parentY, depth=2, buttons=[], currentPos = [0]):

        xPositions = self.DefaultXPos(sentence, xPos, buttons)

        for i in range(1,len(sentence)):
            currentPos[-1] = i
            if isinstance(sentence[i], list):
                if len(sentence[i]) == 2:
                    if isinstance(sentence[i][0], str) and isinstance(sentence[i][1], str):
                        if i == len(sentence) - 1:
                            self.rightmostX = xPositions[i-1]
                        #HEADER
                        self.increseID()

                        #paint the arrow from parent to child node
                        if self.rightmostX >= xPositions[i-1]:
                            if i != len(sentence)-1:
                                xPositions[i-1] = self.rightmostX + self.bWidth + 20

                                for i in range(i,len(xPositions)):
                                    xPositions[i] = xPositions[i-1] + (self.bWidth + 20)

                        paint = {
                            "pxPos" : parentX,
                            "pyPos" : parentY,
                            "cxPos" : xPositions[i-1] + (self.bWidth/2),
                            "cyPos" : (self.bHeight + 40)*depth + (self.bHeight/2)
                        }
                        self.paintPos.append(paint)

                        self.appendPosition(currentPos[:])

                        header = {
                            "id": self.currentID,
                            "name" : sentence[i][0],
                            "xpos" : xPositions[i-1],
                            "ypos" : (self.bHeight + 40)*depth,
                            "position": currentPos[:] + [0]
                        }

                        buttons.append(header)
                        #VALUE
                        self.increseID()

                        #paint the arrow from parent to child node
                        paint2 = {
                            "pxPos" : xPositions[i-1] + (self.bWidth/2),
                            "pyPos" : (self.bHeight + 40)*depth + (self.bHeight/2),
                            "cxPos" : xPositions[i-1] + (self.bWidth/2),
                            "cyPos" : (self.bHeight + 40)*(depth+1) + (self.bHeight/2)
                        }

                        self.paintPos.append(paint2)
                        self.appendPosition(currentPos[:])
                        value = {
                            "id": self.currentID,
                            "name" : sentence[i][1],
                            "xpos" : xPositions[i-1],
                            "ypos" : (self.bHeight + 40)*(depth+1),
                            "position": currentPos[:] + [1]
                        }
                        buttons.append(value)

                    else:

                        self.increseID()

                        #paint the arrow from parent to child node
                        paint = {
                            "pxPos" : parentX,
                            "pyPos" : parentY,
                            "cxPos" : xPositions[i-1] + (self.bWidth/2),
                            "cyPos" : (self.bHeight + 40)*depth + (self.bHeight/2)
                        }
                        self.paintPos.append(paint)

                        self.appendPosition(currentPos[:])

                        header = {
                            "id": self.currentID,
                            "name" : sentence[i][0],
                            "xpos" : xPositions[i-1],
                            "ypos" : (self.bHeight + 40)*depth,
                            "position": currentPos[:] + [0]
                        }

                        buttons.append(header)
                        if isinstance(sentence[i][1][0], str) and isinstance(sentence[i][1][1], str):

                            #HEADER
                            self.increseID()

                            #paint the arrow from parent to child node
                            paint2 = {
                                "pxPos" : xPositions[i-1] + (self.bWidth/2),
                                "pyPos" : (self.bHeight + 40)*depth + (self.bHeight/2),
                                "cxPos" : xPositions[i-1] + (self.bWidth/2),
                                "cyPos" : (self.bHeight + 40)*(depth+1) + (self.bHeight/2)
                            }

                            self.paintPos.append(paint2)
                            self.appendPosition(currentPos[:])

                            header2 = {
                                "id": self.currentID,
                                "name" : sentence[i][1][0],
                                "xpos" : xPositions[i-1],
                                "ypos" : (self.bHeight + 40)*(depth+1),
                                "position": currentPos[:] + [1] + [0]
                            }
                            buttons.append(header2)
                            self.increseID()

                            paint3 = {
                                "pxPos" : xPositions[i-1] + (self.bWidth/2),
                                "pyPos" : (self.bHeight + 40)*(depth+1) + (self.bHeight/2),
                                "cxPos" : xPositions[i-1] + (self.bWidth/2),
                                "cyPos" : (self.bHeight + 40)*(depth+2) + (self.bHeight/2)
                            }

                            self.paintPos.append(paint3)

                            value = {
                                "id": self.currentID,
                                "name" : sentence[i][1][1],
                                "xpos" : xPositions[i-1],
                                "ypos" : (self.bHeight + 40)*(depth+2),
                                "position": currentPos[:] + [1] + [1]
                            }
                            buttons.append(value)
                        else:

                            paint2 = {
                                "pxPos" : xPositions[i-1] + (self.bWidth/2),
                                "pyPos" : (self.bHeight + 40)*depth + (self.bHeight/2),
                                "cxPos" : xPositions[i-1] + + (self.bWidth/2),
                                "cyPos" : (self.bHeight + 40)*(depth+1) + (self.bHeight/2)
                            }

                            self.paintPos.append(paint2)
                            self.increseID()
                            header2 = {
                                "id": self.currentID,
                                "name" : sentence[i][1][0],
                                "xpos" : xPositions[i-1],
                                "ypos" : (self.bHeight + 40)*(depth+1),
                                "position": currentPos[:] + [1] + [0]
                            }

                            tempPos = currentPos[:] + [1] + [0]
                            buttons.append(header2)

                            #define new parent X and parentY
                            npX = xPositions[i-1] + (self.bWidth/2)
                            npY = (self.bHeight + 40)*(depth+1) + (self.bHeight/2)
                            self.createLayout(sentence[i][1], xPositions[i-1], npX, npY, depth+2, buttons, tempPos[:])
                else:

                    tempxPos = xPositions[i-1]
                    if isinstance(sentence[i][1], list):
                        if len(sentence[i]) > 2:

                            tempxPos = xPositions[i-1] + self.bWidth + 20
                        else:
                            print("<=2")

                    self.increseID()
                    self.appendPosition(currentPos[:])

                    paint2 = {
                        "pxPos" : parentX,
                        "pyPos" : parentY,
                        "cxPos" : tempxPos + (self.bWidth/2),
                        "cyPos" : (self.bHeight + 40)*depth + (self.bHeight/2)
                    }
                    self.paintPos.append(paint2)
                    currentPos.append(0)
                    header = {
                        "id" : self.currentID,
                        "name" : sentence[i][0],
                        "xpos" : tempxPos,
                        "ypos" : (self.bHeight + 40)*depth,
                        "position": currentPos[:]
                    }
                    buttons.append(header)

                    #define new parent X and parentY
                    npX = tempxPos + (self.bWidth/2)
                    npY = (self.bHeight + 40)*depth + (self.bHeight/2)

                    self.createLayout(sentence[i], tempxPos, npX, npY, depth+1, buttons, currentPos[:])

            else:
                tempxPos = xPositions[i-1]

                currentPos.append(1)

                self.appendPosition(currentPos)

                value = {
                        "id" : self.currentID,
                        "name" : sentence[i],
                        "xpos" : tempxPos,
                        "ypos" : (self.bHeight + 40)*depth,
                        "position": currentPos[:]
                    }
                buttons.append(value)

        return buttons
            #else:

    def appendPosition(self,pos):
        self.positions = self.positions + [pos]

if __name__ == '__main__':
    app = QApplication(sys.argv)
    ex = App(testVal.tree)
    sys.exit(app.exec_())


import numpy as np
import random
from collections import namedtuple, deque
from model import ANetwork
import torch
import torch.nn.functional as F
import torch.optim as optim
from torch.distributions import Categorical

BUFFER_SIZE  = int(1e4)  # replay buffer size
BATCH_SIZE 	 = 64        # minibatch size
GAMMA 		 = 0.99      # discount factor
LR 			 = 5e-4      # learning rate
UPDATE_EVERY = 4         # how often to update the network

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")

class Agent():          # who will interact with environment
    def __init__(self, state_size, action_size, seed):
        self.seed           = random.seed(seed)     # random seed
        self.time           = 0                     # Initialize time step (for updating every UPDATE_EVERY steps)
        self.state_size     = state_size            # state dimension
        self.action_size    = action_size           # action dimension
        self.policy         = ANetwork(state_size, action_size, seed).to(device)    # policy network
        self.optimizer      = optim.Adam(self.policy.parameters(), lr=LR)       	# optimizer
        self.logprobs 		= []  # using as buffer
        self.rewards  		= []  # using as buffer

    def step(self, state, action, reward, next_state, done):            # Store experience; sometimes call LEARN
        self.rewards.append(reward)
        self.time = (self.time + 1) % UPDATE_EVERY                      # update timer
        if (self.time == 0):                                            # if correct time to learn
            if len(self.rewards) > BATCH_SIZE:                          # wait till memory has BATCH_SIZE number of examples
                self.learn(GAMMA)                         		 		# learn

    def act(self, state, eps=0.0):   # Returns action
        state 	= torch.from_numpy(state).float().unsqueeze(0)
        probs 	= self.policy(state)	# using as buffer
        m 		= Categorical(probs)
        action 	= m.sample()			# select a action
        self.logprobs.append(m.log_prob(action))
        return action.item()

    def learn(self, gamma = GAMMA):	# updates weights
        returns = []
        loss =[]
        R = 0
        for r in self.rewards[::-1]:
            R = r + GAMMA*R
            returns.insert(0, R)				# collect returns
        returns = torch.tensor(returns)
        returns = (returns - returns.mean()) / (returns.std() + 1e-7)	# normalise
        i = 0
        for log_prob, R in zip(self.logprobs, returns):
            if(i>BATCH_SIZE):
                break
            loss.append(-log_prob * R)			# compute loss
        self.optimizer.zero_grad()				# zero out earlier grad
        loss = torch.cat(loss).sum()
        loss.backward()							# backpropagation
        self.optimizer.step()					# update weights
        self.logprobs = []
        self.rewards = []

class ReplayBuffer:
    def __init__(self, action_size, buffer_size, batch_size, seed):
        self.seed           = random.seed(seed)
        self.batch_size     = batch_size
        self.action_size    = action_size
        self.experience     = namedtuple("Experience", field_names=["state", "action", "reward", "next_state", "done"])
        self.memory         = deque(maxlen=buffer_size)

    def __len__(self):
        return len(self.memory)

    def add(self, state, action, reward, next_state, done): # add (s, a, r, s', done)
        e = self.experience(state, action, reward, next_state, done)
        self.memory.append(e)

    def sample(self):
        experiences = []
        for i in range(1, self.batch_size + 1):
            experiences.append(self.memory[-i])

        states      = torch.from_numpy(np.vstack([e.state       for e in experiences if e is not None])).float().to(device)
        actions     = torch.from_numpy(np.vstack([e.action      for e in experiences if e is not None])).long().to(device)
        rewards     = torch.from_numpy(np.vstack([e.reward      for e in experiences if e is not None])).float().to(device)
        next_states = torch.from_numpy(np.vstack([e.next_state  for e in experiences if e is not None])).float().to(device)
        dones       = torch.from_numpy(np.vstack([e.done        for e in experiences if e is not None]).astype(np.uint8)).float().to(device)

        return (states, actions, rewards, next_states, dones)


from collections import defaultdict
class UnionFind(object):
    def __init__(self, accounts):
        self.parent = list(range(10001))
        self.rank = [0] * 10001
    def find(self, x):
        if self.parent[x] == x: # x's parent is its own
            return x
        self.parent[x] = self.find(self.parent[x]) # else search for its parent
        # and change x's parent, a heuristic called path compression
        return self.parent[x]
    def union(self, x, y):
        self.parent[self.find(x)] = self.find(y)
    def union_by_rank(self, x, y):
        rootx = self.find(x)
        rooty = self.find(y)
        if rootx != rooty:
            if self.rank[rootx] > self.rank[rooty]:
                self.parent[rooty] = rootx
            elif self.rank[rootx] < self.rank[rooty]:
                self.parent[rootx] = rooty
            else:
                self.parent[rooty] = rootx
                self.rank[rootx] += 1

class Solution(object):
    def accountsMerge(self, accounts):
        """
        :type accounts: List[List[str]]
        :rtype: List[List[str]]
        """
        uf = UnionFind(accounts)
        email_to_name = {}
        email_to_id = {}
        i = 0
        for acc in accounts:
            name = acc[0]
            for email in acc[1:]:
                email_to_name[email] = name # name can be replaced
                if email not in email_to_id: # but id should be unique
                    email_to_id[email] = i
                    i += 1
                uf.union_by_rank(email_to_id[acc[1]], email_to_id[email]) # Union all element in same list

        ans = defaultdict(list)
        for email in email_to_name:
            parent = uf.find(email_to_id[email])
            ans[parent].append(email)
        return [[email_to_name[v[0]]] + sorted(v) for v in ans.values()]

    # For every pair of emails in the same account, draw an edge between those emails
    # Then dfs to merge the connected components
    def accountsMerge2(self, accounts):
        em_to_name = {}
        graph = defaultdict(set)
        for acc in accounts:
            name = acc[0]
            for email in acc[1:]:
                graph[acc[1]].add(email)
                graph[email].add(acc[1])
                em_to_name[email] = name

        seen = set()
        ans = []
        for email in graph:
            if email not in seen: # visit the unvisited
                seen.add(email) # mark visited
                stack = [email]
                component = []
                while stack: # visit connected component
                    node = stack.pop()
                    component.append(node)
                    for nei in graph[node]:
                        if nei not in seen:
                            seen.add(nei)
                            stack.append(nei)
                ans.append([em_to_name[email]] + sorted(component))
        return ans

accounts = [["John", "johnsmith@mail.com", "john00@mail.com"],
            ["John", "johnnybravo@mail.com"],
            ["John", "johnsmith@mail.com", "john_newyork@mail.com"],
            ["Mary", "mary@mail.com"]]
# accounts = [["Gabe","Gabe0@m.co","Gabe3@m.co","Gabe1@m.co"],
#            ["Kevin","Kevin3@m.co","Kevin5@m.co","Kevin0@m.co"],
#            ["Ethan","Ethan5@m.co","Ethan4@m.co","Ethan0@m.co"],
#            ["Hanzo","Hanzo3@m.co","Hanzo1@m.co","Hanzo0@m.co"],
#            ["Fern","Fern5@m.co","Fern1@m.co","Fern0@m.co"]]
print(Solution().accountsMerge2(accounts))


# python2 compatibility
from __future__ import print_function

# global dates
from datetime import *
today = date.today()
d4 = today.strftime("%d-%b-%Y")
aaa = date.today() + timedelta(days=25)
expDate = aaa.strftime("%d-%b-%Y")
# database connectivity
import sqlite3
conn = sqlite3.connect('library.db')

c = conn.cursor()

class Book:
    count = 104
    def __init__ (self,bid,bname,isAvailable):
        c.execute('''CREATE TABLE IF NOT EXISTS book (bid int UNIQUE, bname text, isAvailable text)''')
        c.execute("INSERT OR REPLACE INTO book (bid, bname, isAvailable) VALUES ('%s','%s','%s')" % (bid,bname,isAvailable))
        # c.execute("INSERT OR REPLACE INTO book VALUES ('102','A good apple','true')")
    def display_books():
        print("-"*66)
        print("| BookId\t|| Book Name\t\t\t || isAvailable\t |")
        print("-"*66)
        bnames = c.execute('SELECT * FROM book')
        for row in bnames:
            print("| ",row[0],"\t\t|| ",row[1]," \t\t || ",row[2],"\t |")
        print("-"*66)
    def addBook():
        Book.count+=1
        newName = input("First Name of new Book ?: ")
        c.execute("INSERT OR REPLACE INTO book (bid, bname,isAvailable) VALUES ('%s','%s','true')" % (Book.count,newName))
        Book.display_books()
    def deleteBook():
        did = input("Enter the bid ?: ")
        c.execute("DELETE FROM book WHERE bid='%s'" % (did))
        print("row deleted")

class Person:
    pid=204
    def __init__(self,pid,pname):
        c.execute('''CREATE TABLE IF NOT EXISTS person (pid int UNIQUE, pname text)''')
        c.execute("INSERT OR REPLACE INTO person (pid, pname) VALUES ('%s','%s')" % (pid,pname))
    def display_people():
        bnames = c.execute('SELECT * FROM person')
        print("-"*40)
        print("| PersonId\t || Person Name\t\t |")
        print("-"*40)
        for row in bnames:
            for item in row:
                print("| ",item,"\t\t",end=" |")
            print("\n|")
        print("-"*40)
    def addPerson():
        Person.pid+=1
        newName = input("First Name of new member ?: ")
        c.execute("INSERT OR REPLACE INTO person (pid, pname) VALUES ('%s','%s')" % (Person.pid,newName))
        Person.display_people()
    def deletePerson():
        did = input("Enter the pid ?: ")
        c.execute("DELETE FROM person WHERE pid='%s'" % (did))
        print("row deleted")

class Transaction(Person,Book):
    count = 500
    def __init__(self):
        c.execute('''CREATE TABLE IF NOT EXISTS transact (tid int UNIQUE,bid int,pid int,lenDate text,expDate text)''')
    def addTransaction():
        bbid = input("Please enter the bid: ")
        ppid = input("Please enter the pid: ")
        res = c.execute("SELECT isAvailable FROM book WHERE bid='%s'"% (bbid))
        for item in res:
            if item[0]=="true":
                print("book available!")
                Transaction.count+=1
                c.execute("INSERT OR REPLACE INTO transact VALUES ('%s', '%s', '%s', '%s', '%s')" % (Transaction.count,bbid,ppid,d4,expDate))
                c.execute("UPDATE book SET isAvailable='false' WHERE bid='%s'"% (bbid))
                Transaction.viewTransaction()
            else:   print("Book is already taken!")
    def submitBook():
        bbid = input("Please enter the bid: ")
        res = c.execute("SELECT isAvailable FROM book WHERE bid='%s'"% (bbid))
        for item in res:
            if item[0]=="false":
                print(" Book Submitted!")
                c.execute("UPDATE book SET isAvailable='true' WHERE bid='%s'"% (bbid))
            else:
                print("Failed! Book is not taken!")
    def viewTransaction():
        print("-"*90)
        print("| TransId || BookID\t || PersonID\t || LendDate\t\t || ExpiryDate\t\t |")
        print("-"*90)
        for row in c.execute('SELECT * FROM transact'):
            for item in row:
                print("| ",item,"\t",end=" |")
            print("\n|")
        print("-"*90)

b1 = Book(101,"A bad Apple","true")
b2 = Book(102,"A good Apple","true")
b3 = Book(103,"A best Apple","true")
b4 = Book(104,"the worst Apple","true")

p1=Person(201,'Rahul')
p2=Person(202,'Manu')
p3=Person(203,'Tany')
p4=Person(204,'Dany')
Transaction()
while True:
    print("\n\n\t\tLibrary Management System\n\n\t(1)Lend Book\n\t(2)Submit Book\n\t(3)View TRansactions\n\t(4)Display Books\n\t(5)Display People\n\t(6)Add Person\n\t(7)Add Book\n\t(8)Delete Person\n\t(9)Delete Book\n\t(0)Quit")
    choice = input(">>> ")
    if choice=="1":
        Transaction.addTransaction()
        continue
    elif choice=="2":
        Transaction.submitBook()
        continue
    elif choice=="3":
        print("Transactions so far")
        Transaction.viewTransaction()
        continue
    elif choice=="4":
        print("Details of books in database")
        Book.display_books()
        continue
    elif choice=="5":
        print("Details of people in database")
        Person.display_people()
        continue
    elif choice=="6":
        print("Adding a new person in database")
        Person.addPerson()
        continue
    elif choice=="7":
        print("Adding a new book in database")
        Book.addBook()
        continue
    elif choice=="8":
        print("Removing new person from database")
        Person.deletePerson()
        continue
    elif choice=="9":
        print("Removing new book from database")
        Book.deleteBook()
    elif choice=="0":
        print("Have a nice day!")
        exit(0)
    else:
        print("Invalid choice, please choose again\n")

conn.commit()
conn.close()


# -*- coding: utf-8 -*-
import json
from os import system, remove

with open('questions.json') as data_file:
	questions = json.load(data_file)

with open('answers.json') as data_file:
	answers = json.load(data_file)

questions = questions['questions']
answers = answers['answers']

for question in questions:
	#Generar el tsv con los datos
	for answer in answers:
		if answer['questionId'] == question['id']:
			respuestas = answer['answers']
			yrange = max(answer['answers'])

	respuestastsv = ''
	cont = 0
	for res in question['answers']:
		respuestastsv += str(respuestas[res['id']-1]) + ' ' + res['text'].encode('utf-8')
		cont+=1

	file = open('respuestas.tsv', 'w+')
	file.write(respuestastsv) # python will convert \n to os.linesep
	file.close()

	gnuplot = "\
	# ______________________________________________________________________\n\
	#Setting output\n\
	set term png\n\
	set output \"./Plots/plot"+str(question['id'])+".png\"\n\
	# For the next graph, we want a histogram.\n\
	set style data boxes\n\
	# set xrange [0:"+str(question['numAnswers'])+"]\n\
	# set yrange [0:"+str(yrange)+"]\n\
	# set xtics rotate by -45\n\
	\n\
	# We want a small gap between solid (filled-in) bars.\n\
	set boxwidth 0.8 relative\n\
	set style fill solid 1.0\n\
	\n\
	# Plot the histogram (one curve).\n\
	plot 'respuestas.tsv' using 1:xtic(2) with boxes title '"+question['text'].strip('\n').encode('utf-8')+"'\n\
	"
	file = open('tmp.gp', 'w+')
	file.write(gnuplot) # python will convert \n to os.linesep
	file.close()
#
#
#
#
	system('gnuplot tmp.gp')
	remove('respuestas.tsv')
	remove('tmp.gp')


from os import urandom
import logging
import os

SITE_ROOT = os.path.dirname(os.path.realpath(os.path.dirname(__file__)))

class BaseConfig():
    LOGGING_CONFIG = {
        "version": 1,
        "disable_existing_loggers": False,
        "formatters": {
            "simple": {
                "format": "[%(asctime)s] [Level: %(levelname)s] [Path : %(pathname)s]  :Message: %(message)s\n\r"
            }
        },

        "handlers": {
            "console": {
                "class": "logging.StreamHandler",
                "level": "DEBUG",
                "formatter": "simple",
                "stream": "ext://sys.stdout"
            },

            "root_file_handler": {
                "class": "logging.handlers.WatchedFileHandler",
                "level": "DEBUG",
                "formatter": "simple",
                "filename": "Flask.log",
                "encoding": "utf8"
            },

            "application_file_handler": {
                "class": "logging.handlers.WatchedFileHandler",
                "level": "DEBUG",
                "formatter": "simple",
                "filename": "application.log",
                "encoding": "utf8"
            }
        },

        "loggers": {
            "question_answer_app": {
                "level": "DEBUG",
                "handlers": ["application_file_handler"],
                "propagate": "no"
            }
        }
    }


#
# Lead-acid higher-order models (FOQS and Composite)
#
import pybamm
from .base_lead_acid_model import BaseModel

class HigherOrderBaseModel(BaseModel):
    """Base model for higher-order models for lead-acid, from [1]_.
    Uses leading-order model from :class:`pybamm.lead_acid.LOQS`

    References
    ----------
    .. [1] V Sulzer, SJ Chapman, CP Please, DA Howey, and CW Monroe. Faster Lead-Acid
           Battery Simulations from Porous-Electrode Theory: II. Asymptotic Analysis.
           arXiv preprint arXiv:1902.01774, 2019.

    **Extends:** :class:`pybamm.lead_acid.BaseModel`
    """

    def __init__(self, options=None, name="Composite model"):
        super().__init__(options, name)

        self.set_leading_order_model()
        self.set_reactions()
        self.set_current_collector_submodel()
        # Electrolyte submodel to get first-order concentrations
        self.set_electrolyte_diffusion_submodel()
        self.set_other_species_diffusion_submodels()
        # Average interface submodel to get average first-order potential differences
        self.set_average_interfacial_submodel()
        # Electrolyte and solid submodels to get full first-order potentials
        self.set_negative_electrode_submodel()
        self.set_electrolyte_conductivity_submodel()
        self.set_positive_electrode_submodel()
        # Update interface, porosity and convection with full potentials
        self.set_full_interface_submodel()
        self.set_full_convection_submodel()
        self.set_full_porosity_submodel()
        self.set_thermal_submodel()

        self.build_model()

    def set_leading_order_model(self):
        leading_order_model = pybamm.lead_acid.LOQS(
            self.options, name="LOQS model (for composite model)"
        )
        self.update(leading_order_model)
        self.reaction_submodels = leading_order_model.reaction_submodels

        # Leading-order variables
        leading_order_variables = {}
        for variable in self.variables.keys():
            leading_order_variables[
                "Leading-order " + variable.lower()
            ] = leading_order_model.variables[variable]
        self.variables.update(leading_order_variables)
        self.variables[
            "Leading-order electrolyte concentration change"
        ] = leading_order_model.rhs[
            leading_order_model.variables["Average electrolyte concentration"]
        ]

    def set_current_collector_submodel(self):
        self.submodels["current collector"] = pybamm.current_collector.Uniform(
            self.param
        )

    def set_average_interfacial_submodel(self):
        self.submodels[
            "average negative interface"
        ] = pybamm.interface.lead_acid.InverseFirstOrderKinetics(self.param, "Negative")
        self.submodels[
            "average negative interface"
        ].reaction_submodels = self.reaction_submodels["Negative"]
        self.submodels[
            "average positive interface"
        ] = pybamm.interface.lead_acid.InverseFirstOrderKinetics(self.param, "Positive")
        self.submodels[
            "average positive interface"
        ].reaction_submodels = self.reaction_submodels["Positive"]

    def set_electrolyte_conductivity_submodel(self):
        self.submodels[
            "electrolyte conductivity"
        ] = pybamm.electrolyte.stefan_maxwell.conductivity.FirstOrder(self.param)

    def set_negative_electrode_submodel(self):
        self.submodels["negative electrode"] = pybamm.electrode.ohm.Composite(
            self.param, "Negative"
        )

    def set_positive_electrode_submodel(self):
        self.submodels["positive electrode"] = pybamm.electrode.ohm.Composite(
            self.param, "Positive"
        )

    def set_full_interface_submodel(self):
        """
        Set full interface submodel, to get spatially heterogeneous interfacial current
        densities
        """
        # Main reaction
        self.submodels[
            "negative interface"
        ] = pybamm.interface.lead_acid.FirstOrderButlerVolmer(self.param, "Negative")
        self.submodels[
            "positive interface"
        ] = pybamm.interface.lead_acid.FirstOrderButlerVolmer(self.param, "Positive")

        # Oxygen
        if "oxygen" in self.options["side reactions"]:
            self.submodels[
                "positive oxygen interface"
            ] = pybamm.interface.lead_acid_oxygen.FirstOrderForwardTafel(
                self.param, "Positive"
            )
            self.submodels[
                "negative oxygen interface"
            ] = pybamm.interface.lead_acid_oxygen.FullDiffusionLimited(
                self.param, "Negative"
            )

    def set_full_convection_submodel(self):
        """
        Update convection submodel, now that we have the spatially heterogeneous
        interfacial current densities
        """
        if self.options["convection"] is False:
            self.submodels["full convection"] = pybamm.convection.NoConvection(
                self.param
            )
        if self.options["convection"] is True:
            self.submodels["full convection"] = pybamm.convection.Composite(self.param)

    def set_full_porosity_submodel(self):
        """
        Update porosity submodel, now that we have the spatially heterogeneous
        interfacial current densities
        """
        self.submodels["full porosity"] = pybamm.porosity.Full(self.param)

    @property
    def default_solver(self):
        """
        Create and return the default solver for this model
        """
        # Different solver depending on whether we solve ODEs or DAEs
        if self.options["surface form"] == "algebraic":
            return pybamm.ScikitsDaeSolver()
        else:
            return pybamm.ScipySolver()

class FOQS(HigherOrderBaseModel):
    """First-order quasi-static model for lead-acid, from [1]_.
    Uses leading-order model from :class:`pybamm.lead_acid.LOQS`

    References
    ----------
    .. [1] V Sulzer, SJ Chapman, CP Please, DA Howey, and CW Monroe. Faster Lead-Acid
           Battery Simulations from Porous-Electrode Theory: II. Asymptotic Analysis.
           arXiv preprint arXiv:1902.01774, 2019.

    **Extends:** :class:`pybamm.lead_acid.HigherOrderBaseModel`
    """

    def __init__(self, options=None, name="FOQS model"):
        super().__init__(options, name)

    def set_electrolyte_diffusion_submodel(self):
        self.submodels[
            "electrolyte diffusion"
        ] = pybamm.electrolyte.stefan_maxwell.diffusion.FirstOrder(
            self.param, self.reactions
        )

    def set_other_species_diffusion_submodels(self):
        if "oxygen" in self.options["side reactions"]:
            self.submodels["oxygen diffusion"] = pybamm.oxygen_diffusion.FirstOrder(
                self.param, self.reactions
            )

    def set_full_porosity_submodel(self):
        """
        Update porosity submodel, now that we have the spatially heterogeneous
        interfacial current densities
        """
        # TODO: fix shape for jacobian
        pass

class Composite(HigherOrderBaseModel):
    """Composite model for lead-acid, from [1]_.
    Uses leading-order model from :class:`pybamm.lead_acid.LOQS`

    References
    ----------
    .. [1] V Sulzer, SJ Chapman, CP Please, DA Howey, and CW Monroe. Faster Lead-Acid
           Battery Simulations from Porous-Electrode Theory: II. Asymptotic Analysis.
           arXiv preprint arXiv:1902.01774, 2019.

    **Extends:** :class:`pybamm.lead_acid.HigherOrderBaseModel`
    """

    def __init__(self, options=None, name="Composite model"):
        super().__init__(options, name)

    def set_electrolyte_diffusion_submodel(self):
        self.submodels[
            "electrolyte diffusion"
        ] = pybamm.electrolyte.stefan_maxwell.diffusion.Composite(
            self.param, self.reactions
        )

    def set_other_species_diffusion_submodels(self):
        if "oxygen" in self.options["side reactions"]:
            self.submodels["oxygen diffusion"] = pybamm.oxygen_diffusion.Composite(
                self.param, self.reactions
            )

    def set_full_porosity_submodel(self):
        """
        Update porosity submodel, now that we have the spatially heterogeneous
        interfacial current densities
        """
        self.submodels["full porosity"] = pybamm.porosity.Full(self.param)

class CompositeExtended(HigherOrderBaseModel):
    """Extended composite model for lead-acid.
    Uses leading-order model from :class:`pybamm.lead_acid.LOQS`

    **Extends:** :class:`pybamm.lead_acid.HigherOrderBaseModel`
    """

    def __init__(self, options=None, name="Extended composite model"):
        super().__init__(options, name)

    def set_electrolyte_diffusion_submodel(self):
        self.submodels[
            "electrolyte diffusion"
        ] = pybamm.electrolyte.stefan_maxwell.diffusion.Composite(
            self.param, self.reactions, extended=True
        )

    def set_other_species_diffusion_submodels(self):
        if "oxygen" in self.options["side reactions"]:
            self.submodels["oxygen diffusion"] = pybamm.oxygen_diffusion.Composite(
                self.param, self.reactions, extended=True
            )

    def set_full_porosity_submodel(self):
        """
        Update porosity submodel, now that we have the spatially heterogeneous
        interfacial current densities
        """
        self.submodels["full porosity"] = pybamm.porosity.Full(self.param)


import numpy as np
import pandas as pd
import os

import matplotlib.pyplot as plt
import seaborn as sns

import sklearn.metrics as metrics
import sklearn.tree as tree
from sklearn.tree import DecisionTreeClassifier
from sklearn.neighbors import KNeighborsClassifier
from sklearn.cross_validation import train_test_split

path = 'data/credit-data.csv'

#### LOAD DATA ####
def load_data(path, index_col = None):
	'''
	Load data into pandas from a csv

	Inputs:
	- path (str): Path to location of csv file
	- index_col (str): column to specify as index, defaults to None

	Returns pandas dataframe
	'''
	if os.path.exists(path):
	    df = pd.read_csv(path)
	else:
		raise Exception('The file does not exist at this location')

	return df

#### EXPLORE DATA ####
def make_histogram(df, var_of_interest, kde = True, rug = False):
	'''
	Make histograms of continuous variables using Seaborn "distplot" function

	Inputs:
	- df (DataFrame): Dataset of interest
	- var_of_interest (str): continuous variable to visualize
	- kde (bool): If true, show distribution trend line
	- rug (bool): If true, show exact numerical location of observations

	No return, shows a histogram
	'''
	# Note: NaN's are exluded
	plot_var = df[var_of_interest]
	sns.distplot(plot_var[~plot_var.isnull()], kde = kde, rug = rug)
	plt.title(var_of_interest + ' Histogram')
	plt.ylabel('Frequency')
	plt.show()

def make_countchart(df, var_of_interest):
	'''
	Make countchart of categorical variables using Seaborn "countplot" function

	Inputs:
	- df (DataFrame): Dataset of interest
	- var_of_interest (str): continuous variable to visualize

	No return, shows a countplot
	'''
	plot_var = df[var_of_interest]
	sns.countplot(plot_var, data=df)
	plt.title(var_of_interest + ' Countplot')
	plt.show()

def check_correlations(df):
	'''
	Check correlations between all variables in a dataframe

	Inputs:
	- df (DataFrame): Dataset of interest

	Returns a pandas datafarme
	'''
	return df.corr()

def find_high_corr(corr_matrix, threshold, predictor_var):
	'''
	Find all variables that are highly correlated with the predictor and thus
	likely candidates to exclude

	Inputs
	- corr_matrix (DataFrame): Result of the "check_correlations" function
	- threshold (int): Value between 0 and 1
	- predictor_var (str): Predictor variable

	Returns list of variables highly correlated with the predictor_var
	'''
	return corr_matrix[corr_matrix[predictor_var] > threshold].index

def plot_correlations(df, x, y, hue = None, fitreg = False):
	'''
	Make a scatter plot of using the Seaborn lmplot function

	Inputs:
	- df (DataFrame): Dataset of interest
	- x, y (strs): Variables to identify as x and y
	- hue (str): Optional third variable to color datapoints
	- fitreg (bool): Option to include a fitline

	No return, shows a scatter plot
	'''
	sns.lmplot(x, y, hue = hue, fit_reg = fitreg, data = df)
	plt.title(x + ' vs ' + y)
	plt.show()

#### DATA PRE-PROCESSING/CLEANING ####
def fill_nulls(df):
	'''
	Find values in a dataframe with null values and fill them with the median
	value of that variable

	Inputs:
	- df (DataFrame): Dataset of interest

	Returns the original dataframe with null values filled
	'''
	# Find columns with missing values
	isnull = df.isnull().any()
	isnull_cols = list(isnull[isnull == True].index)

	# Fill nulls with median
	for col in isnull_cols:
		col_mean = df[col].median()
		df[col].fillna(col_mean, inplace = True)

	return df

#### GENERATE FEATURES/PREDICTIONS ####
def x_y_generator(df, feature_cols, predictor_col):
	'''
	Build feature and predictor portions of the dataset

	Inputs:
	- df (DataFrame): Dataset of interest
	- feature_cols (list): Columns to keep as features
	- precictor_col (str): Column to specify as predictor

	Returns a dataframe of features and a dataframe of the predictor
	'''
	all_cols = df.columns
	drop_cols = set(all_cols) - set(feature_cols)

	X = df.drop(drop_cols, axis = 1)

	Y = df[predictor_col]

	return X, Y

def cat_to_dummy(df, var_of_interest):
	'''
	Turn a categorical/discrete variable into a dummy variable

	Inputs:
	- df (DataFrame): Dataset of interest
	- var_of_interest (str): variable to dummify

	Returns an updated dataframe
	'''
	return pd.get_dummies(df, columns = [var_of_interest])

def continuous_to_cat(df, var_of_interest, bins = 10, labels = False):
	'''
	Turn a continuous variable into a categorical variable

	Inputs:
	- df (DataFrame): Dataset of interest
	- var_of_interest (str): variable to categorize
	- bins (int): Number of bins to separate data into
	- labels (bool): Indications whether data should be shown as a range or
	numerical value

	Returns an updated dataframe
	'''
	return pd.cut(df[var_of_interest], bins, labels = labels)

#### BUILD CLASSIFIER ####
def build_knn(num_neighbors, weights = None, metric = None, metric_params=None):
	'''
	Build a k nearest neighbor classifier using sklearn functionality

	Inputs:
	- num_neighbors (int): number of neighbors
	- weights (str): How to weight the neighbors
	- metric (str): distance function to use to evaluate neighbors
	- metric_params (dict): specify additional metric parameters as needed

	Returns a classifier
	'''
	model = KNeighborsClassifier(n_neighbors=num_neighbors, weights=weights,
		metric=metric, metric_params=metric_params)

	return model

def build_tree():
	'''
	To build later
	'''
	pass

def proba_wrap(model, x_data, predict = False, threshold = 0.5):
	'''
	To build later
	'''
	return model.predict_proba(x_data)

#### EVALUATE CLASSIFIER ####
def check_accuracy(y_actual, y_predict):
	'''
	Check accuracy of a prediction

	Inputs:
	- y_actual (Series): Actual predictor values from original dataset
	- y_predict (Series): Predicted values produced by classifier

	Returns float value between 0 and 1
	'''
	return metrics.accuracy_score(y_actual, y_predict)

def precision(y_actual, y_predict):
	'''
	Check precision of a prediction

	Inputs:
	- y_actual (Series): Actual predictor values from original dataset
	- y_predict (Series): Predicted values produced by classifier

	Returns float value between 0 and 1
	'''
	return metrics.precision_score(y_actual, y_predict)

def confusion_matrix(y_actual, y_predict):
	'''
	Build confusion matrix based on actual and predicted values

	Inputs:
	- y_actual (Series): Actual predictor values from original dataset
	- y_predict (Series): Predicted values produced by classifier

	Returns a confusion matrix
	'''
	return metrics.confusion_matrix(y_actual, y_predict, labels=None,
		sample_weight=None)

def knn_evaluation_matrix(k_range, x_train, y_train, x_test, y_test,
	metrics = ['minkowski','euclidean', 'manhattan'],
	weight_funcs = ['uniform', 'distance']):
	'''
	Evaluate models with a variety of different parameters

	Returns a dataframe
	'''
	df = pd.DataFrame(columns = ['num_neighbors', 'metric',
		'weighting_function', 'training_acc', 'test_acc', 'train_confusion', 'test_confusion'])
	i = 0

	for k in k_range:
	    for metric in metrics:
	        for func in weight_funcs:
	            params = [k, metric, func]
	            model = build_knn(k, func, metric)
	            model.fit(x_train, y_train)
	            train_pred = model.predict(x_train)
	            test_pred = model.predict(x_test)
	            train_acc = check_accuracy(y_train, train_pred)
	            test_acc = check_accuracy(y_test, test_pred)
	            train_confusion = confusion_matrix(y_train, train_pred)
	            test_confusion = confusion_matrix(y_test, test_pred)
	            tup = [k, metric, func, train_acc, test_acc, train_confusion, test_confusion]
	            df.loc[i] = tup
	            i += 1

	return df


from OpenGL import GL

from .vertex import ProgramVertexAttribute
from .uniform_block import ProgramUniformBlock

from contextlib import contextmanager

import ctypes as c

shader_types = { 'vertex': GL.GL_VERTEX_SHADER
			   , 'fragment': GL.GL_FRAGMENT_SHADER
			   , 'geometry': GL.GL_GEOMETRY_SHADER
			   , 'tesselation control': GL.GL_TESS_CONTROL_SHADER
			   , 'tesselation evaluation': GL.GL_TESS_EVALUATION_SHADER }
shader_types.update({t: t for t in shader_types.values()})

class Shader:
	def __init__(self, source, shader_type):
		self.shader_type = shader_types[shader_type]
		self.handle = GL.glCreateShader(self.shader_type)
		if self.handle == 0:
			raise RuntimeError("Failed to create shader.")

		GL.glShaderSource(self.handle, source)
		GL.glCompileShader(self.handle)
		if GL.glGetShaderiv(self.handle, GL.GL_COMPILE_STATUS) == GL.GL_FALSE:
			log = GL.glGetShaderInfoLog(self.handle).decode()
			raise RuntimeError("Failed to compile {}: \n\n{}".format(self.shader_type, log))

	def delete(self):
		'''Delete the shader to free up GL resources.

		.. warning::

		   Do not use the shader object after running this method.
		'''

		GL.glDeleteShader(self.handle)

class Program:
	"""An OpenGL program.

	:param shaders: The shaders. It is good practice to delete them after creating the program.
	:type shaders: [:py:class:`Shader`]
	:param attributes: The vertex attributes used in the program
	:type attributes: [:py:class:`.VertexAttribute`]
	:param uniform_blocks: The uniform blocks defined in the program
	:type uniform_blocks: [:py:class:`.UniformBlock`]
	"""

	def __init__(self, shaders, vertex_attributes=None, uniform_blocks=None,
	             xfb_varyings=None, xfb_mode=GL.GL_INTERLEAVED_ATTRIBS):
		self.handle = GL.glCreateProgram()
		if self.handle == 0:
			raise RuntimeError("Failed to create OpenGL program.")

		for shader in shaders:
			GL.glAttachShader(self.handle, shader.handle)

		self.xfb_varyings = xfb_varyings
		if xfb_varyings is not None:
			varyings = (c.c_char_p * len(xfb_varyings))(*(v.name.encode() for v in self.xfb_varyings))
			varyings = c.cast(varyings, c.POINTER(c.POINTER(c.c_char)))
			GL.glTransformFeedbackVaryings(self.handle, len(xfb_varyings), varyings, xfb_mode)
		self._xfb_mode = xfb_mode

		GL.glLinkProgram(self.handle)
		if GL.glGetProgramiv(self.handle, GL.GL_LINK_STATUS) == GL.GL_FALSE:
			log = GL.glGetProgramInfoLog(self.handle).decode()
			GL.glDeleteProgram(self.handle)
			raise RuntimeError("Failed to link program: \n\n{}".format(log))

		self.uniform_blocks = { ub.name: ProgramUniformBlock.fromUniformBlock(self, ub)
		                        for ub in uniform_blocks or []}
		self.vertex_attributes = { v.name: ProgramVertexAttribute.fromVertexAttribute(self, v)
		                           for v in vertex_attributes or [] }

	@property
	def xfb_mode(self):
		return self._xfb_mode

	@xfb_mode.setter
	def xfb_mode(self, xfb_mode):
		varyings = (c.c_char_p * len(xfb_varyings))(*(v.name for v in self.xfb_varyings))
		varyings = c.cast(varyings, c.POINTER(c.POINTER(c.c_char)))
		GL.glTransformFeedbackVaryings(self.handle, len(xfb_varyings), varyings, xfb_mode)
		self._xfb_mode = xfb_mode

	@classmethod
	def fromSources(cls, sources, **kwargs):
		shaders = []
		try:
			for shader_type, shader in sources.items():
				shaders.append(Shader(shader, shader_type))
			program = cls(shaders, **kwargs)
		finally:
			for shader in shaders:
				shader.delete()
		return program

	def __enter__(self):
		'''Programs provide a context manager that binds and then unbinds them.

		.. _program-bind-warning:
		.. warning::

		   It is not allowed to bind multiple programs (or one program multiple times).

		   Methods that bind a program will be documented.
		'''
		GL.glUseProgram(self.handle)

	def __exit__(self, ty, val, tr):
		GL.glUseProgram(0)

	@contextmanager
	def feedback(self, mode=GL.GL_POINTS):
		GL.glBeginTransformFeedback(mode)
		yield
		GL.glEndTransformFeedback()


import json
from functools import wraps

from flask import abort, jsonify, make_response, request, Blueprint, Response
from flask.views import MethodView
from flask.ext.login import login_required
from sqlalchemy.exc import SQLAlchemyError

from pwryoursite import app, session
from pwryoursite.forms.semester import NewSemesterForm, DeleteSemesterForm
from pwryoursite.models import Semester
from pwryoursite.controllers.authentication import teacher_required

mod = Blueprint('restful', __name__, url_prefix='/api')

def _json(func):
    @wraps(func)
    def wrapper(*args, **kwargs):
        response = func(*args, **kwargs)
        if not isinstance(response, Response):
            raise TypeError('only response are supported')
        response.headers['Content-Type'] = 'text/json'
        return response
    return wrapper

class SemesterView(MethodView):

    @_json
    def get(self):
        return make_response(json.dumps([s.to_json() for s in Semester.query.all()]))

    @teacher_required
    @login_required
    def post(self):
        app.logger.debug(request.form)
        form = NewSemesterForm(request.form)
        if not form.validate():
            return abort(400)

        s = Semester(form.name.data, form.year.data)
        session.add(s)
        try:
            session.commit()
        except SQLAlchemyError as err:
            app.logger.exception('commiting transaction failed: %s', err)
            session.rollback()
            return abort(500)

        return make_response(jsonify(s.to_json()), 201)

    @teacher_required
    @login_required
    @_json
    def delete(self):
        form = DeleteSemesterForm(request.form)
        form.semester_ids.choices = [(s.id, s) for s in Semester.query.all()]
        if not form.validate():
            return abort(400)

        to_remove = []
        for id in form.semester_ids.data:
            s = Semester.query.get(id)
            if s is None:
                app.logger.warning('trying to remove nonexisting Semester record, id=%d', id)
            for c in s.courses:
                session.delete(c)
            to_remove.append(id)
            session.delete(s)

        try:
            session.commit()
        except SQLAlchemyError as e:
            app.logger.error('commiting object removal failed, error=%s', e)
            session.rollback()
            return abort(500)
        return make_response(json.dumps(to_remove))

mod.add_url_rule('/semesters/', view_func=SemesterView.as_view('semesters'))


from django.urls import path,include,re_path
from BookGuan.views import *
urlpatterns = [
    path('register/',register),
    path('login/',login),
    path('index/',index),
    path('logout/',logout),
    path('base/',base),
    path('goods_list/',goods_list),
    re_path("goods_list/(?P<status>[01])/(?P<page>\d+)/", goods_list),
    re_path("goods_status/(?P<status>\w+)/(?P<id>\d+)/", goods_status),
    path('goods_add/', goods_add),
    path('personal_info/',personal_info),
    # path('personal_info/', personal_info),
]


# RPS Component 3 - Compare user choice and computer choice

rps_list = ["rock", "paper", "scissors"]

comp_index = 0

for item in rps_list:
    user_index = 0
    for item in rps_list:
        user_choice = rps_list[user_index]
        comp_choice = rps_list[comp_index]
        user_index += 1

        # Compare options...
        if comp_choice == user_choice:
            result = "It's a tie"
        elif user_choice == "rock" and comp_choice == "scissors":
            result = "You won!"
        elif user_choice == "paper" and comp_choice == "rock":
            result = "You won!"
        elif user_choice == "scissors" and comp_choice == "paper":
            result = "You won!"
        else:
            result = "You lost (better luck next time)"

        print("You chose {}, the computer chose {}.  \nResult: {}".format(user_choice, comp_choice, result))

    comp_index += 1
    print()


from __future__ import unicode_literals
import frappe
from frappe.model.naming import make_autoname, getseries
from frappe.utils import flt, cint, nowdate,get_datetime
from frappe.utils import cstr
from frappe import _

def on_submit(self, method=None):
	if self.purpose == "Material Transfer" or self.purpose == "Material Receipt":
		store_managers = frappe.db.sql("select parent from tabUserRole where role = 'Emi Store Manager' and parent <> 'Administrator'",as_dict=True)
		if store_managers:
			store_managers_list = [manager.get('parent') for manager in store_managers]
			if store_managers_list:
				for manager in store_managers_list:
					name = frappe.db.get_value("User",{"name":manager},"first_name")
					#stock_entry_submit_notification(self.name,manager,name,self.purpose,self.items)

def stock_entry_submit_notification(name,recp,recpname,purpose,items):
	date = frappe.utils.get_datetime(nowdate()).strftime("%d-%m-%Y")
	# try:
	# 	frappe.sendmail(
	# 		recipients=[recp,"onkar.m@indictranstech.com","prashant.j@indictranstech.com"],
	# 		#recipients=[recp,"sankar@emiuae.ae","emistores@emiuae.ae","harish@emiuae.ae"],
	# 		expose_recipients="header",
	# 		sender=frappe.session.user,
	# 		reply_to=None,
	# 		subject= purpose+" Notification for "+ name,
	# 		content=None,
	# 		reference_doctype=None,
	# 		reference_name=None,
	# 		message = frappe.render_template("templates/email/stock_entry_notification.html", {"Name":recpname,"purpose":purpose,"items":items,"date":date}),
	# 		message_id=None,
	# 		unsubscribe_message=None,
	# 		delayed=False,
	# 		communication=None
	# 	)
	# except Exception,e:
	# 	frappe.throw(("Mail has not been Sent. Kindly Contact to Administrator"))

@frappe.whitelist()
def validate(self,method=None):
	for row in self.items:
		if len(self.items) > 8:
			if float(row.idx) % 8 == 0:
				row.page_break = 1