import atexit
import os
import re
import socket
import threading
import uuid

from tensorflow.core.framework import graph_debug_info_pb2
from tensorflow.core.framework import tensor_pb2
from tensorflow.core.protobuf import debug_event_pb2
from tensorflow.python.debug.lib import debug_events_writer
from tensorflow.python.debug.lib import op_callbacks_common
from tensorflow.python.debug.lib import source_utils
from tensorflow.python.eager import function as function_lib
from tensorflow.python.framework import constant_op
from tensorflow.python.framework import dtypes
from tensorflow.python.framework import op_callbacks
from tensorflow.python.framework import ops
from tensorflow.python.framework import tensor_util
from tensorflow.python.ops import array_ops
from tensorflow.python.ops import gen_debug_ops
from tensorflow.python.platform import tf_logging as logging
from tensorflow.python.util import compat
from tensorflow.python.util import object_identity
from tensorflow.python.util import tf_stack
from tensorflow.python.util.tf_export import tf_export

_state = threading.local()
DEFAULT_TENSOR_DEBUG_MODE = "NO_TENSOR"

# pylint:disable=protected-access
_FUNCTION_PREFIXES = (
    compat.as_bytes(function_lib._FORWARD_PREFIX),
    compat.as_bytes(function_lib._BACKWARD_PREFIX),
    compat.as_bytes(function_lib._INFERENCE_PREFIX))
# pylint:enable=protected-access

def is_op_type_function(op_type):
  return compat.as_bytes(op_type).startswith(_FUNCTION_PREFIXES)

@ops.RegisterGradient("DebugIdentityV2")
def _debug_identity_v2_grad(op, dy):
  """Gradient function for the DebugIdentityV2 op."""
  del op  # Unused
  return dy

def _get_tfdbg_run_id():
  return str(uuid.uuid4())[:8]

def _get_id():
  """Get a short unique ID."""
  return str(uuid.uuid4())

def _concrete_tensor_to_proto(tensor):
  return tensor_util.make_tensor_proto(tensor.numpy())

class _DumpingCallback(object):
  """An object holding the states surrounding the dumping callback."""

  def __init__(self,
               dump_root,
               tensor_debug_mode,
               circular_buffer_size,
               op_regex,
               tensor_dtypes):
    self._dump_root = dump_root
    self._tfdbg_run_id = _get_tfdbg_run_id()
    self._tensor_debug_mode = tensor_debug_mode
    self._circular_buffer_size = circular_buffer_size
    self._op_regex = op_regex
    self._tensor_dtypes = tensor_dtypes

    self._hostname = socket.gethostname()
    # A list of source-file paths.
    self._source_file_paths = []
    # A map from stack frame (FileLineCol) to unique ID.
    self._stack_frame_to_id = dict()
    # Mapping op context to unique ID.
    self._context_to_id = dict()
    self._function_to_graph_id = dict()
    self._op_type_to_context_id = dict()
    # Keeps track of counter for symbolic tensors output by in-graph ops.
    # It is used to make unique names for debugger-generated tensors.
    self._symbolic_tensor_counter = 0
    # A map from the names of debugger-generated Identity and DebugIdentityV2
    # tensors to the names of the original insrumented graph tensors. This is
    # applicable to v1 graph mode only.
    self._tensor_aliases = dict()
    self._source_file_paths_lock = threading.Lock()
    self._stack_frame_to_id_lock = threading.Lock()
    self._context_lock = threading.Lock()
    self._symbolic_tensor_counter_lock = threading.Lock()
    # A dict mapping Placeholder tensors to their instrumenting debug tensors.
    # Used only under V1 graph mode, where we can't rely on auto control
    # dependency to execute the debug tensors and hence need to attach the debug
    # tensors as control dependencies of the ops that consume the Placeholder.
    self._placeholder_to_debug_tensor = (
        object_identity.ObjectIdentityDictionary())
    self._writer = None

  def function_callback(self, function):
    """A callback to be called on creation of ConcreteFunctions."""
    graph_id = self._get_context_id(function.graph)
    with self._context_lock:
      # NOTE(cais): We currently store the function (ConcreteFunction)
      # as keys of this dict, because weakrefs to them sometimes become
      # unreferenceable by the time the op callback is called. This approach
      # may cause memory leaks due to the holding of the functions. If that's
      # the case, calling `tf.debugging.disable_dump_debug_info()` should
      # cause GC of this object and this dict.
      self._function_to_graph_id[function] = graph_id

  @property
  def dump_root(self):
    return self._dump_root

  @dump_root.setter
  def dump_root(self, dump_root):
    if self._dump_root != dump_root:
      self._dump_root = dump_root
      self._writer = None

  @property
  def tfdbg_run_id(self):
    return self._tfdbg_run_id

  @property
  def tensor_debug_mode(self):
    return self._tensor_debug_mode

  @property
  def circular_buffer_size(self):
    return self._circular_buffer_size

  def get_writer(self):
    """Get the debug events writer for the currently configured dump root."""
    if not self._writer:
      self._writer = debug_events_writer.DebugEventsWriter(
          self._dump_root,
          self._tfdbg_run_id,
          circular_buffer_size=self._circular_buffer_size)
    return self._writer

  def _get_context_id(self, context):
    """Get a unique ID for an op-construction context (e.g., a graph).

    If the graph has been encountered before, reuse the same unique ID.
    When encountering a new context (graph), this methods writes a DebugEvent
    proto with the debugged_graph field to the proper DebugEvent file.

    Args:
      context: A context to get the unique ID for. Must be hashable. E.g., a
        Graph object.

    Returns:
      A unique ID for the context.
    """
    # Use the double-checked lock pattern to optimize the common case.
    if context in self._context_to_id:  # 1st check, without lock.
      return self._context_to_id[context]
    graph_is_new = False
    with self._context_lock:
      if context not in self._context_to_id:  # 2nd check, with lock.
        graph_is_new = True
        context_id = _get_id()
        self._context_to_id[context] = context_id
    if graph_is_new:
      self.get_writer().WriteDebuggedGraph(debug_event_pb2.DebuggedGraph(
          graph_id=context_id,
          graph_name=getattr(context, "name", None),
          outer_context_id=self._get_outer_context_id(context)))
    return self._context_to_id[context]

  def _get_outer_context_id(self, graph):
    """Get the ID of the immediate outer context of the input graph.

    Args:
      graph: The graph (context) in question.

    Returns:
      If an outer context exists, the immediate outer context name as a string.
      If such as outer context does not exist (i.e., `graph` is itself
      outermost), `None`.
    """
    if hasattr(graph, "outer_graph") and graph.outer_graph:
      return self._get_context_id(graph.outer_graph)
    else:
      return None

  def _write_source_file_content(self, file_path):
    """Send the content of a source file via debug-events writer.

    Args:
      file_path: Path to the source file.

    Returns:
      An int index for the file.
    """
    if file_path in self._source_file_paths:
      return self._source_file_paths.index(file_path)
    with self._source_file_paths_lock:
      if file_path not in self._source_file_paths:
        lines = None
        if source_utils.is_extension_uncompiled_python_source(file_path):
          try:
            lines, _ = source_utils.load_source(file_path)
          except IOError as e:
            logging.warn(
                "Failed to read source code from path: %s. Reason: %s",
                file_path, e)
        writer = self.get_writer()
        writer.WriteSourceFile(debug_event_pb2.SourceFile(
            file_path=file_path, host_name=self._hostname, lines=lines))
        self._source_file_paths.append(file_path)
      return self._source_file_paths.index(file_path)

  def _process_stack_frames(self):
    """Process stack frames.

    Send the content of source-files, on a best-effort basis.

    Returns:
      A list of stack frame IDs.
    """
    stack_frames = tf_stack.extract_stack()
    stack_frame_ids = []
    writer = None
    for file_path, lineno, func, _ in stack_frames:
      abs_path = os.path.abspath(file_path)
      if (abs_path, lineno, func) in self._stack_frame_to_id:
        stack_frame_ids.append(
            self._stack_frame_to_id[(abs_path, lineno, func)])
        continue
      with self._stack_frame_to_id_lock:
        if (abs_path, lineno, func) not in self._stack_frame_to_id:
          stack_frame_id = _get_id()
          self._stack_frame_to_id[(abs_path, lineno, func)] = stack_frame_id
          file_index = self._write_source_file_content(abs_path)
          file_line_col = graph_debug_info_pb2.GraphDebugInfo.FileLineCol(
              file_index=file_index, line=lineno, func=func)
          stack_frame_with_id = debug_event_pb2.StackFrameWithId(
              id=stack_frame_id, file_line_col=file_line_col)
          writer = self.get_writer()
          writer.WriteStackFrameWithId(stack_frame_with_id)
        stack_frame_ids.append(
            self._stack_frame_to_id[(abs_path, lineno, func)])

    code_location = debug_event_pb2.CodeLocation(
        host_name=self._hostname, stack_frame_ids=stack_frame_ids)
    return code_location

  def _process_v1_graph_mode_tensor(self,
                                    op_type,
                                    tensor,
                                    debug_tensor,
                                    tensor_debug_mode):
    """For V1 graph mode, determine what tensor to output from callback.

    Args:
      op_type: Type of the op that outputs the original symbolic tensor.
      tensor: The original output symbolic tensor.
      debug_tensor: The debugger-instrumented tensor.
      tensor_debug_mode: Debug mode used, a tfdbg TensorDebugMode enum.

    Returns:
      A symbolic tensor to be returned by the dumping op_callback.
    """
    # Placeholders need special treatment under V1 graph mode. The
    # callback can't simply override the Placeholder tensor to a debug tensor,
    # as that would cause the Placeholder op to lack a value.
    if op_type in ("Placeholder", "PlaceholderWithDefault"):
      self._placeholder_to_debug_tensor[tensor] = debug_tensor
      return tensor
    else:
      # TODO(cais): Evaluate performance optimization options. For the
      # `NO_TENSOR` debug mode, an alternative is to add `debug_tensor` as a
      # control dependency of `tensor.op` without an additional identity op.
      if (tensor_debug_mode == debug_event_pb2.TensorDebugMode.FULL_TENSOR and
          op_type != "Const"):
        # NOTE(b/153716279): Under v1 graph mode, overriding the output tensor
        # of Const ops can lead to downstream errors related to shapes. We opt
        # to use an identity op to avoid this issue at the cost of slightly
        # larger graph size.
        self._tensor_aliases[debug_tensor.name] = tensor.name
        return debug_tensor
      else:
        with self._symbolic_tensor_counter_lock:
          identity_name = "tfdbg_identity_%d" % self._symbolic_tensor_counter
        identity = array_ops.identity(tensor, name=identity_name)
        identity.op._add_control_input(  # pylint: disable=protected-access
            debug_tensor.op)
        self._tensor_aliases[identity.name] = tensor.name
        return identity

  def _instrument_symbolic_tensors(self,
                                   tensors,
                                   op_type,
                                   op_name,
                                   tfdbg_context_id,
                                   tensor_ids):
    """Add debugging instrumentation for symbolic (i.e., non-eager) tensors.

    The detailed fashion in which the tensors are instrumented is determined
    by the tensor_debug_mode configured for the currently enabled dumping
    callback.

    Args:
      tensors: A tuple of Tensors to instrument. It is assumed that their
        ordering corresponds to the ordering of output tensors of an original
        op. Output slot indices (0-based) will be generated based on the
        ordering.
      op_type: Type name of the op that emits the Tensors (e.g., "MatMul").
      op_name: Name of the op that emits the Tensors (e.g., "dense_1/MatMul").
      tfdbg_context_id: A unique ID for the context that the op belongs to
        (e.g., a graph).
      tensor_ids: A list of unique ID numbers for the tensors, for tfdbg's
        internal use.

    Returns:
      Non-eager Tensors that override the `tensors` as the output of the op
      that originally generated `tensors`. In some cases (e.g., non-V1 graph
      mode), this may be `None`, as the instrumentation can simply rely on
      automatic control dependencies (see `auto_control_deps.py`) instead of
      tensor overriding.
    """
    tensor_debug_mode = self._tensor_debug_mode
    debug_urls = ["file://%s" % self._dump_root]
    is_v1_graph_mode = not ops.executing_eagerly_outside_functions()
    instrumented_tensors = [] if is_v1_graph_mode else None
    for output_slot, tensor in enumerate(tensors):
      with self._symbolic_tensor_counter_lock:
        debug_identity_name = ("DebugIdentityV2_%d" %
                               self._symbolic_tensor_counter)
      debug_identity_op_kwargs = {
          "tfdbg_context_id": tfdbg_context_id,
          "op_name": op_name,
          "output_slot": output_slot,
          "tensor_debug_mode": self._tensor_debug_mode,
          "debug_urls": debug_urls,
          "name": debug_identity_name,
          "circular_buffer_size": self._circular_buffer_size,
          "tfdbg_run_id": self._tfdbg_run_id,
      }
      if tensor_debug_mode == debug_event_pb2.TensorDebugMode.NO_TENSOR:
        if (not self._should_dump_tensor(op_type, tensor.dtype) or
            not tensor.dtype.is_numpy_compatible):
          if is_v1_graph_mode:
            instrumented_tensors.append(tensor)
          continue
        if is_v1_graph_mode and not tensor.dtype.is_numpy_compatible:
          # Avoid instrumenting Placeholder under is_v1_graph_mode. Doing that
          # would cause runtime complaint about Placeholders not being fed.
          instrumented_tensors.append(tensor)
          continue
        # Except in V1 graph mode + control flow, debug_identity_v2 triggers
        # auto control dependency because it's a stateful op.
        debug_tensor = gen_debug_ops.debug_identity_v2(
            # Use an empty (shape=[0]) float32 tensor for the NO_TENSOR mode
            # as a low-overhead placeholder, since no actual tensor value is
            # traced.
            constant_op.constant([], dtype=dtypes.float32),
            **debug_identity_op_kwargs)
        if is_v1_graph_mode:
          instrumented_tensors.append(self._process_v1_graph_mode_tensor(
              op_type, tensor, debug_tensor, tensor_debug_mode))
      elif tensor_debug_mode in (debug_event_pb2.TensorDebugMode.CURT_HEALTH,
                                 debug_event_pb2.TensorDebugMode.CONCISE_HEALTH,
                                 debug_event_pb2.TensorDebugMode.FULL_HEALTH,
                                 debug_event_pb2.TensorDebugMode.SHAPE):
        dtype = tensor.dtype
        dtype_is_dumpable = (
            tensor_debug_mode in (
                debug_event_pb2.TensorDebugMode.CURT_HEALTH,
                debug_event_pb2.TensorDebugMode.CONCISE_HEALTH,
                debug_event_pb2.TensorDebugMode.FULL_HEALTH) and
            dtype.is_floating or
            tensor_debug_mode == debug_event_pb2.TensorDebugMode.SHAPE and
            (dtype.is_floating or dtype.is_integer or dtype.is_bool))
        if (not self._should_dump_tensor(op_type, tensor.dtype) or
            not dtype_is_dumpable):
          if is_v1_graph_mode:
            instrumented_tensors.append(tensor)
          continue
        debug_tensor = gen_debug_ops.debug_identity_v2(
            gen_debug_ops.debug_numeric_summary_v2(
                tensor,
                tensor_id=tensor_ids[output_slot],
                tensor_debug_mode=self._tensor_debug_mode,
                output_dtype=dtypes.float64), **debug_identity_op_kwargs)
        if is_v1_graph_mode:
          instrumented_tensors.append(self._process_v1_graph_mode_tensor(
              op_type, tensor, debug_tensor, tensor_debug_mode))
      elif tensor_debug_mode == debug_event_pb2.TensorDebugMode.FULL_TENSOR:
        if (not self._should_dump_tensor(op_type, tensor.dtype) or
            not tensor.dtype.is_numpy_compatible):
          # Instrumenting DT_VARIANT and DT_RESOURCE type tensors under
          # V1 graph mode is known to have issues. TODO(cais): Investigate.
          if is_v1_graph_mode:
            instrumented_tensors.append(tensor)
          continue
        debug_tensor = gen_debug_ops.debug_identity_v2(
            tensor, **debug_identity_op_kwargs)
        if is_v1_graph_mode:
          instrumented_tensors.append(self._process_v1_graph_mode_tensor(
              op_type, tensor, debug_tensor, tensor_debug_mode))
      else:
        raise NotImplementedError(
            "Symbolic tensor instrumentation is not implemented for debug mode "
            "%s" % self._tensor_debug_mode)
    return instrumented_tensors

  def _dump_eager_tensors(self,
                          tensors,
                          op_type,
                          input_tensor_ids,
                          output_tensor_device_ids,
                          graph_id=None):
    """Dump the value of eager tensors.

    The destination of the dumping is determined by the dump_root of the
    currently enabled dumping callback. The tensors may be transformed prior to
    dumping (e.g., reduced as summary statistics such as minimum, maximum and
    arithmetic  mean). The details of this transformation (if any) depends on
    the tensor_debug_mode of the currently enabled dumping callback.

    Args:
      tensors: The EagerTensors whose values are to be dumped, with or without
        value transform.
      op_type: Type of the op that generates the tensors, as a string.
      input_tensor_ids: IDs of the input EagerTensors to the op.
      output_tensor_device_ids: Debugged-generated IDs for the devices on which
        the output tensors are allocated, as a `list` of `int`s. Must match
        `tensors` in length.
      graph_id: ID of the executed graph, applicable only to eager execution of
        a FuncGraph.

    Returns:
      A tfdbg Execution protocol buffer.
    """
    tensor_debug_mode = self._tensor_debug_mode
    output_tensor_ids = [
        t._id for t in tensors]  # pylint:disable=protected-access
    assert len(tensors) == len(output_tensor_device_ids)
    if tensor_debug_mode == debug_event_pb2.TensorDebugMode.NO_TENSOR:
      return debug_event_pb2.Execution(
          op_type=op_type,
          graph_id=graph_id,
          num_outputs=len(tensors),
          input_tensor_ids=input_tensor_ids,
          output_tensor_ids=output_tensor_ids,
          output_tensor_device_ids=output_tensor_device_ids,
          tensor_debug_mode=tensor_debug_mode,
          code_location=self._process_stack_frames())
    elif tensor_debug_mode in (debug_event_pb2.TensorDebugMode.CURT_HEALTH,
                               debug_event_pb2.TensorDebugMode.CONCISE_HEALTH,
                               debug_event_pb2.TensorDebugMode.FULL_HEALTH,
                               debug_event_pb2.TensorDebugMode.SHAPE,
                               debug_event_pb2.TensorDebugMode.FULL_TENSOR):
      execution_proto = debug_event_pb2.Execution(
          op_type=op_type,
          num_outputs=len(tensors),
          graph_id=graph_id,
          input_tensor_ids=input_tensor_ids,
          output_tensor_ids=output_tensor_ids,
          output_tensor_device_ids=output_tensor_device_ids,
          tensor_debug_mode=tensor_debug_mode,
          code_location=self._process_stack_frames())
      for tensor in tensors:
        if (self._should_dump_tensor(op_type, tensor.dtype) and
            tensor.dtype.is_numpy_compatible):
          if tensor_debug_mode in (
              debug_event_pb2.TensorDebugMode.CURT_HEALTH,
              debug_event_pb2.TensorDebugMode.CONCISE_HEALTH,
              debug_event_pb2.TensorDebugMode.FULL_HEALTH):
            if tensor.dtype.is_floating:
              tensor_proto = _concrete_tensor_to_proto(
                  gen_debug_ops.debug_numeric_summary_v2(
                      tensor,
                      tensor_debug_mode=tensor_debug_mode,
                      output_dtype=dtypes.float64))
            else:
              # A placeholder for non-floating-type output tensors.
              tensor_proto = tensor_pb2.TensorProto()
          elif tensor_debug_mode == debug_event_pb2.TensorDebugMode.SHAPE:
            if (tensor.dtype.is_floating or tensor.dtype.is_integer or
                tensor.dtype.is_bool):
              tensor_proto = _concrete_tensor_to_proto(
                  gen_debug_ops.debug_numeric_summary_v2(
                      tensor,
                      tensor_debug_mode=tensor_debug_mode,
                      output_dtype=dtypes.float64))
            else:
              # A placeholder for non-floating-type output tensors.
              tensor_proto = tensor_pb2.TensorProto()
          elif tensor_debug_mode == debug_event_pb2.TensorDebugMode.FULL_TENSOR:
            tensor_proto = _concrete_tensor_to_proto(tensor)
          if tensor_proto:
            execution_proto.tensor_protos.append(tensor_proto)
      return execution_proto
    else:
      raise NotImplementedError(
          "Tensor instrumentation is not implemented for debug mode %s yet " %
          self._tensor_debug_mode)

  def callback(self,
               op_type,
               inputs,
               attrs,
               outputs,
               op_name=None,
               graph=None):
    """Op callback for tracing (dumping) a TF program's execution."""
    del attrs  # Unused

    writer = self.get_writer()
    if graph:
      is_v1_graph_mode = not ops.executing_eagerly_outside_functions()
      context_id = self._get_context_id(graph)  # Innermost context ID.
      output_tensor_ids = self._get_symbolic_tensor_ids(len(outputs))
      if op_type in ("Const", "Placeholder", "PlaceholderWithDefault"):
        # In some cases, the op name of a Const or Placeholder op in a graph
        # can be duplicate (e.g., `None` or "resource").
        # When this happens, we use the output tensor name to infer
        # the non-duplicated tensor name.
        op_name = outputs[0].name.split(":")[0]
      if is_v1_graph_mode:
        for input_tensor in inputs:
          if input_tensor in self._placeholder_to_debug_tensor and outputs:
            outputs[0].op._add_control_input(  # pylint: disable=protected-access
                self._placeholder_to_debug_tensor[input_tensor].op)
      graph_op_creation = debug_event_pb2.GraphOpCreation(
          op_type=op_type,
          op_name=op_name,
          graph_name=graph.name if hasattr(graph, "name") else None,
          graph_id=context_id,
          input_names=[
              self._lookup_tensor_name(input_tensor) for input_tensor in inputs
          ],
          num_outputs=len(outputs),
          output_tensor_ids=output_tensor_ids,
          code_location=self._process_stack_frames())
      writer.WriteGraphOpCreation(graph_op_creation)
      if outputs and compat.as_bytes(
          op_type) not in op_callbacks_common.OP_CALLBACK_SKIP_OPS:
        return self._instrument_symbolic_tensors(
            outputs, op_type, op_name, context_id, output_tensor_ids)
    else:
      op_type_bytes = compat.as_bytes(op_type)
      if op_type_bytes == b"DebugNumericSummaryV2":
        # TODO(b/140334369): Remove this special casing logic once op_callback.
        # automatically prevents infinite recursion in eager mode.
        return None
      if op_type_bytes in op_callbacks_common.OP_CALLBACK_SKIP_OPS:
        return None
      context_id = self._func_graph_id_from_func_name(op_type)
      input_ids = [t._id for t in inputs]  # pylint:disable=protected-access
      output_tensor_device_ids = [writer.RegisterDeviceAndGetId(output.device)
                                  for output in outputs] if outputs else []
      writer.WriteExecution(self._dump_eager_tensors(
          outputs, op_type, input_ids, output_tensor_device_ids,
          graph_id=context_id))

  def _lookup_tensor_name(self, tensor):
    """Look up the name of a graph tensor.

    This method maps the name of a debugger-generated Identity or
    DebugIdentityV2 tensor to the name of the original instrumented tensor,
    if `tensor` is such a debugger-created tensor.
    Otherwise, it returns the name of `tensor` as is.

    Args:
      tensor: The graph tensor to look up the name for.

    Returns:
      Name of the original instrumented tensor as known to the debugger.
    """
    return self._tensor_aliases.get(tensor.name, tensor.name)

  def _func_graph_id_from_func_name(self, op_type):
    """Attempt to get the ID of a FuncGraph based on an op type name.

    Also caches the ID for faster access later.

    Args:
      op_type: Op type string, which may be the name of a function.

    Returns:
      If the op_type name does not fit the pattern of a function name (e.g.,
      one that starts with "__inference_"), `None` is returned immediately.
      Else, if the FuncGraph is found, ID of the underlying FuncGraph is
      returned as a string.
      Else, `None` is returned.
    """
    op_type = compat.as_bytes(op_type)
    if is_op_type_function(op_type):
      # op_type for eagerly-executed FuncGraphs have the prefixed and suffixed
      # form such as "__inference_my_function_13579", wherein the middle part
      # "my_function" is the name of the Python function from which the
      # FuncGraph is compiled. Due to the suffix, the op_type is unique for
      # - duplicate Python function names
      # - multiple compilation of the same Python function
      if op_type in self._op_type_to_context_id:
        return self._op_type_to_context_id[op_type]
      with self._context_lock:
        for function in self._function_to_graph_id:
          if function.name == op_type:
            graph_id = self._function_to_graph_id[function]
            self._op_type_to_context_id[op_type] = graph_id
            return graph_id
      return None
    else:
      return None

  def _get_symbolic_tensor_ids(self, num_tensors):
    tensor_ids = []
    if num_tensors:
      with self._symbolic_tensor_counter_lock:
        for _ in range(num_tensors):
          self._symbolic_tensor_counter += 1
          tensor_ids.append(self._symbolic_tensor_counter)
    return tensor_ids

  def _should_dump_tensor(self, op_type, dtype):
    """Determine if the given tensor's value will be dumped.

    The determination is made given the configurations such as `op_regex`,
    `tensor_dtypes`.

    Args:
      op_type: Name of the op's type, as a string (e.g., "MatMul").
      dtype: The dtype of the tensor, as a `dtypes.DType` object.

    Returns:
      A bool indicating whether the tensor's value will be dumped.
    """
    should_dump = True
    if self._op_regex:
      should_dump = (should_dump and
                     re.match(self._op_regex, op_type))
    if self._tensor_dtypes:
      if isinstance(self._tensor_dtypes, (list, tuple)):
        should_dump = (should_dump and
                       any(dtype == dtype_item for dtype_item
                           in self._tensor_dtypes))
      else:  # A callable that takes a DType argument and return a boolean.
        should_dump = should_dump and self._tensor_dtypes(dtype)
    return should_dump

@tf_export("debugging.experimental.enable_dump_debug_info")
def enable_dump_debug_info(dump_root,
                           tensor_debug_mode=DEFAULT_TENSOR_DEBUG_MODE,
                           circular_buffer_size=1000,
                           op_regex=None,
                           tensor_dtypes=None):
  """Enable dumping debugging information from a TensorFlow program.

  The debugging information is dumped to a directory on the file system
  specified as `dump_root`.

  The dumped debugging information can be ingested by debugger UIs.

  The files in the dump directory contain the following information:
    - TensorFlow Function construction (e.g., compilation of Python functions
      decorated with @tf.function), the op types, names (if available), context,
      the input and output tensors, and the associated stack traces.
    - Execution of TensorFlow operations (ops) and Functions and their stack
      traces, op types, names (if available) and contexts. In addition,
      depending on the value of the `tensor_debug_mode` argument (see Args
      section below), the value(s) of the output tensors or more concise
      summaries of the tensor values will be dumped.
    - A snapshot of Python source files involved in the execution of the
      TensorFlow program.

  Once enabled, the dumping can be disabled with the corresponding
  `disable_dump_debug_info()` method under the same Python namespace.
  Calling this method more than once with the same `dump_root` is idempotent.
  Calling this method more than once with different `tensor_debug_mode`s
  leads to a `ValueError`.
  Calling this method more than once with different `circular_buffer_size`s
  leads to a `ValueError`.
  Calling this method with a different `dump_root` abolishes the
  previously-enabled `dump_root`.

  Usage example:

  ```py
  tf.debugging.experimental.enable_dump_debug_info('/tmp/my-tfdbg-dumps')

  # Code to build, train and run your TensorFlow model...
  ```

  NOTE: If your code is running on TPUs, be sure to call
  `tf.config.set_soft_device_placement(True)` before calling
  `tf.debugging.experimental.enable_dump_debug_info()` as this API uses
  automatic outside compilation on TPUs. For example:

  ```py
  tf.config.set_soft_device_placement(True)
  tf.debugging.experimental.enable_dump_debug_info(
      logdir, tensor_debug_mode="FULL_HEALTH")

  resolver = tf.distribute.cluster_resolver.TPUClusterResolver(tpu='')
  strategy = tf.distribute.TPUStrategy(resolver)
  with strategy.scope():
    # ...
  ```

  Args:
    dump_root: The directory path where the dumping information will be written.
    tensor_debug_mode: Debug mode for tensor values, as a string.
      The currently supported options are:
      - "NO_TENSOR": (Default) Only traces the output tensors of all executed
        ops (including those executed eagerly at the Python level or as a part
        of a TensorFlow graph) and functions, while not extracting any
        information from the values of the tensors.
      - "CURT_HEALTH": For each floating-dtype tensor (e.g., tensors of dtypes
        such as `float32`, `float64` and `bfloat16`), extracts a binary bit
        indicating whether it contains any -infinity, +infinity or NaN.
      - "CONCISE_HEALTH": For each floating-dtype tensor, extract total
        element count, and counts of -infinity, +infinity and NaN elements.
      - "FULL_HEALTH": For each floating-dtype tensor, extracts the dtype,
        rank (number of dimensions), total element count, and counts of
        -infinity, +infinity and NaN elements.
      - "SHAPE": For each tensor (regardless of dtype), extracts its dtype,
        rank, total element count and shape.
    circular_buffer_size: Size of the circular buffers for execution events.
      These circular buffers are designed to reduce the overhead of debugging
      dumping. They hold the most recent debug events concerning eager execution
      of ops and `tf.function`s and traces of tensor values computed inside
      `tf.function`s. They are written to the file system only when the proper
      flushing method is called (see description of return values below).
      Expected to be an integer. If <= 0, the circular-buffer behavior will be
      disabled, i.e., the execution debug events will be written to the file
      writers in the same way as non-execution events such as op creations and
      source-file snapshots.
    op_regex: Dump data from only the tensors from op types that matches to the
      regular expression (through Python's `re.match()`).
      "Op type" refers to the names of the TensorFlow operations (e.g.,
      "MatMul", "LogSoftmax"), which may repeat in a TensorFlow
      function. It does *not* refer to the names of nodes (e.g.,
      "dense/MatMul", "dense_1/MatMul_1") which are unique within a function.
      - Example 1: Dump tensor data from only MatMul and Relu ops
        `op_regex="^(MatMul|Relu)$"`.
      - Example 2: Dump tensors from all ops *except* Relu:
        `op_regex="(?!^Relu$)"`.
      This filter operates in a logical AND relation with `tensor_dtypes`.
    tensor_dtypes: Dump data from only the tensors of which the specified
      dtypes. This optional argument can be in any of the following format:
      - a list or tuple of `DType` objects or strings that can be converted
        to `DType` objects via `tf.as_dtype()`. Examples:
        - `tensor_dtype=[tf.float32, tf.float64]`,
        - `tensor_dtype=["float32", "float64"]`,
        - `tensor_dtypes=(tf.int32, tf.bool)`,
        - `tensor_dtypes=("int32", "bool")`
      - a callable that takes a single `DType` argument and returns a Python
        `boolean` indicating whether the dtype is to be included in the data
        dumping. Examples:
        - `tensor_dtype=lambda dtype: dtype.is_integer`.
      This filter operates in a logical AND relation with `op_regex`.
  Returns:
    A DebugEventsWriter instance used by the dumping callback. The caller
    may use its flushing methods, including `FlushNonExecutionFiles()` and
    `FlushExecutionFiles()`.
  """
  # TODO(cais): Revise the "UIs (currently under construction)" part of the doc
  # string above.
  # TODO(cais): Add Python code example to the doc string above.
  global _state

  tensor_debug_mode_keys = debug_event_pb2.TensorDebugMode.keys()
  if tensor_debug_mode not in tensor_debug_mode_keys:
    raise ValueError(
        "Invalid value in tensor_debug_mode ('%s'). Valid options are: %s" %
        (tensor_debug_mode, tensor_debug_mode_keys))

  tensor_debug_mode = debug_event_pb2.TensorDebugMode.Value(tensor_debug_mode)
  if tensor_debug_mode not in (debug_event_pb2.TensorDebugMode.NO_TENSOR,
                               debug_event_pb2.TensorDebugMode.CURT_HEALTH,
                               debug_event_pb2.TensorDebugMode.CONCISE_HEALTH,
                               debug_event_pb2.TensorDebugMode.FULL_HEALTH,
                               debug_event_pb2.TensorDebugMode.SHAPE,
                               debug_event_pb2.TensorDebugMode.FULL_TENSOR):
    raise NotImplementedError(
        "tfdbg dumping: support for tensor debug mode %s is not "
        "implemented yet" %
        debug_event_pb2.TensorDebugMode.Name(tensor_debug_mode))

  # Validate the types of tensor_dtypes.
  if tensor_dtypes is not None:
    if (not isinstance(tensor_dtypes, (list, tuple)) and
        not callable(tensor_dtypes)):
      raise ValueError(
          "If specified, tensor_dtypes is expected to be a list, a tuple, or "
          "a callable that takes a DType argument and returns a boolean, "
          "but received %s" % (tensor_dtypes,))
    if isinstance(tensor_dtypes, (list, tuple)):
      tensor_dtypes = [
          dtypes.as_dtype(dtype_item) for dtype_item in tensor_dtypes]

  if hasattr(_state, "dumping_callback"):
    if _state.dumping_callback.circular_buffer_size != circular_buffer_size:
      raise ValueError(
          "There is already a dumping callback configured with a different "
          "circular-buffer size (%d). Therefore the newly request "
          "circular-buffer size (%d) will not be honored." %
          (_state.dumping_callback.circular_buffer_size, circular_buffer_size))
    if _state.dumping_callback.tensor_debug_mode != tensor_debug_mode:
      raise ValueError(
          "There is already a dumping callback configured for dump root "
          "%s with a different "
          "tensor-debug mode (%s). Therefore the newly request "
          "tensor-debug mode (%s) size will not be honored." %
          (_state.dumping_callback.dump_root,
           tensor_debug_mode_keys[_state.dumping_callback.tensor_debug_mode],
           tensor_debug_mode_keys[tensor_debug_mode]))
  else:
    _state.dumping_callback = _DumpingCallback(dump_root,
                                               tensor_debug_mode,
                                               circular_buffer_size,
                                               op_regex,
                                               tensor_dtypes)
    op_callbacks.add_op_callback(_state.dumping_callback.callback)
    function_lib.CONCRETE_FUNCTION_CALLBACKS.append(
        _state.dumping_callback.function_callback)

  if _state.dumping_callback.dump_root != dump_root:
    _state.dumping_callback.dump_root = dump_root

  logging.info(
      "Enabled dumping callback in thread %s "
      "(dump root: %s, tensor debug mode: %s)",
      threading.current_thread().name,
      _state.dumping_callback.dump_root,
      debug_event_pb2.TensorDebugMode.Name(tensor_debug_mode))

  atexit.register(disable_dump_debug_info)
  return _state.dumping_callback.get_writer()

@tf_export("debugging.experimental.disable_dump_debug_info")
def disable_dump_debug_info():
  """Disable the currently-enabled debugging dumping.

  If the `enable_dump_debug_info()` method under the same Python namespace
  has been invoked before, calling this method disables it. If no call to
  `enable_dump_debug_info()` has been made, calling this method is a no-op.
  Calling this method more than once is idempotent.
  """
  if hasattr(_state, "dumping_callback"):
    dump_root = _state.dumping_callback.dump_root
    tfdbg_run_id = _state.dumping_callback.tfdbg_run_id
    debug_events_writer.DebugEventsWriter(dump_root, tfdbg_run_id).Close()
    op_callbacks.remove_op_callback(_state.dumping_callback.callback)
    if (
        _state.dumping_callback.function_callback
        in function_lib.CONCRETE_FUNCTION_CALLBACKS
    ):
      function_lib.CONCRETE_FUNCTION_CALLBACKS.remove(
          _state.dumping_callback.function_callback
      )
    delattr(_state, "dumping_callback")
    logging.info("Disabled dumping callback in thread %s (dump root: %s)",
                 threading.current_thread().name, dump_root)


# Created by matveyev at 04.08.2021
import numpy as np
from PyQt5 import QtWidgets, QtCore
import logging

from petra_viewer.main_window import APP_NAME
from petra_viewer.gui.array_selector_ui import Ui_ArraySelector

logger = logging.getLogger(APP_NAME)

class ArraySelector(QtWidgets.QWidget):

    def __init__(self, my_id, cut_selector, data_pool, frame_viewer):
        """
        """
        super(ArraySelector, self).__init__()
        self._ui = Ui_ArraySelector()
        self._ui.setupUi(self)

        self._my_id = my_id
        self.limit_range = 0
        self._max_frame = 0

        self._old_z_min = None
        self._old_z_max = None

        self._data_pool = data_pool
        self._frame_viewer = frame_viewer
        self._cut_selector = cut_selector

        self._ui.sl_frame.valueChanged.connect(self._update_from_frame_slider)
        self._ui.sl_range.sliderMoved.connect(self._update_from_range_slider)

        self._ui.but_first.clicked.connect(lambda: self._update_from_navigation_buttons('first'))
        self._ui.but_previous.clicked.connect(lambda: self._update_from_navigation_buttons('previous'))
        self._ui.but_next.clicked.connect(lambda: self._update_from_navigation_buttons('next'))
        self._ui.but_last.clicked.connect(lambda: self._update_from_navigation_buttons('last'))

        self._ui.sp_value_from.editingFinished.connect(self._update_from_sp)
        self._ui.sp_value_to.editingFinished.connect(self._update_from_sp)

        self._ui.cmd_show_all.clicked.connect(self._show_all)

    # ----------------------------------------------------------------------
    def _new_cut(self):
        self._old_z_min = self._ui.sl_range.low()
        self._old_z_max = self._ui.sl_range.high()
        self._cut_selector.new_range(self._my_id)

    # ----------------------------------------------------------------------
    def switch_integration_mode(self, state):

        self._ui.sl_frame.setVisible(not state)
        self._ui.sl_range.setVisible(state)
        self._ui.sp_value_to.setVisible(state)
        self._ui.lb_to.setVisible(state)

    # ----------------------------------------------------------------------
    def _show_all(self):
        self._set_spin_boxes(0, self._max_frame)
        self._set_slider_value(0, self._max_frame)

        self._old_z_min = self._ui.sl_range.low()
        self._old_z_max = self._ui.sl_range.high()

        self._cut_selector.set_integration(self._my_id)

    # ----------------------------------------------------------------------
    def _update_from_frame_slider(self, value):
        """
        Set value of range slider and spin_boxes
        """
        self._set_spin_boxes(value, value)
        self._set_slider_value(value, value)
        self._new_cut()

    # ----------------------------------------------------------------------
    def _update_from_range_slider(self, z_min, z_max):
        """
        Set value of range slider and spin_boxes
        """
        z_min, z_max = self._apply_range_limit(z_min, z_max)
        self._set_spin_boxes(z_min, z_max)
        self._set_slider_value(z_min, z_max)
        self._new_cut()

    # ----------------------------------------------------------------------
    def _update_from_navigation_buttons(self, mode):
        """
        Process button_clink on navigation buttons
        """
        z_min = self._ui.sl_range.low()
        z_max = self._ui.sl_range.high()

        new_min = z_min
        new_max = z_max

        if mode == 'first':
            new_min = 0
        elif mode == 'previous':
            new_min -= 1
            new_max -= 1
        elif mode == 'next':
            new_min += 1
            new_max += 1
        elif mode == 'last':
            new_max = self._max_frame
            if z_min == z_max:
                new_min = self._max_frame

        new_max = min(max(0, new_max), self._max_frame)
        new_min = min(max(0, new_min), self._max_frame)
        new_min, new_max = self._apply_range_limit(new_min, new_max)

        self._set_spin_boxes(new_min, new_max)
        self._set_slider_value(new_min, new_max)
        self._new_cut()

    # ----------------------------------------------------------------------
    def _update_from_sp(self):
        """
        Set values to slider given in the SpinBox.
        """
        z_min = self._value_to_frame(self._ui.sp_value_from.value())
        z_max = self._value_to_frame(self._ui.sp_value_to.value())
        z_min, z_max = self._apply_range_limit(z_min, z_max)

        if z_min != self._old_z_min or z_max != self._old_z_max:
            self._set_slider_value(z_min, z_max)
            self._set_spin_boxes(z_min, z_max)
            self._new_cut()

    # ----------------------------------------------------------------------
    def _set_slider_value(self, z_min, z_max):
        """
        Set values to slider.
        """
        self.blockSignals(True)
        self._ui.sl_frame.setValue(z_min)
        self._ui.sl_range.setLow(z_min)
        self._ui.sl_range.setHigh(z_max)
        self.blockSignals(False)

    # ----------------------------------------------------------------------
    def units_changed(self):
        self.blockSignals(True)

        decimals = self._data_pool.get_axis_resolution(self._frame_viewer.current_file(), self._my_id)

        self._ui.sp_value_from.setDecimals(decimals)
        self._ui.sp_value_from.setSingleStep(np.power(10., -decimals))
        self._ui.sp_value_to.setDecimals(decimals)
        self._ui.sp_value_to.setSingleStep(np.power(10., -decimals))

        self._ui.sp_step.setDecimals(decimals)
        self._ui.sp_step.setSingleStep(np.power(10., -decimals))
        self._ui.sp_step.setValue(np.power(10., -decimals))

        self._ui.sl_range.setSingleStep(np.power(10., -decimals))
        self._ui.sl_frame.setSingleStep(np.power(10., -decimals))

        self._ui.sp_value_to.setMinimum(self._frame_to_value(0))
        self._ui.sp_value_to.setMaximum(self._frame_to_value(self._ui.sl_range.maximum()))

        self._ui.sp_value_from.setMinimum(self._frame_to_value(0))
        self._ui.sp_value_from.setMaximum(self._frame_to_value(self._ui.sl_range.maximum()))

        self._ui.sp_value_from.setValue(self._frame_to_value(self._ui.sl_range.low()))
        self._ui.sp_value_to.setValue(self._frame_to_value(self._ui.sl_range.high()))

        self.blockSignals(False)

    # ----------------------------------------------------------------------
    def _frame_to_value(self, frame):
        return self._data_pool.get_value_for_frame(self._frame_viewer.current_file(), self._my_id, frame)

    # ----------------------------------------------------------------------
    def _value_to_frame(self, frame):
        return self._data_pool.get_frame_for_value(self._frame_viewer.current_file(), self._my_id, frame)

    # ----------------------------------------------------------------------
    def _set_spin_boxes(self, z_min, z_max):
        """
        Set value of spin_boxes sp_value_from, sp_value_to and sp_step
        """
        self.blockSignals(True)

        z_min = self._frame_to_value(z_min)
        z_max = self._frame_to_value(z_max)

        self._ui.sp_value_from.setValue(z_min)
        self._ui.sp_value_to.setValue(z_max)

        self.blockSignals(False)

    # ----------------------------------------------------------------------
    def _apply_range_limit(self, z_min, z_max):
        z_min = max(0, min(z_min, z_max))
        if 0 < self.limit_range - 1 < (z_max - z_min):
            z_max = self.limit_range + z_min - 1
        return z_min, z_max

    # ----------------------------------------------------------------------
    def setup_limits(self, max_frame):
        """
        Set limits to sliders and spin_boxes
        """
        self.blockSignals(True)
        logger.debug(f"Setup_limits of {max_frame} for {self._my_id}")

        self._ui.sl_frame.setMaximum(max_frame)
        self._ui.sl_range.setMaximum(max_frame)
        self._ui.sl_range.setMinimum(0)

        self._ui.sp_value_to.setMinimum(self._frame_to_value(0))
        self._ui.sp_value_to.setMaximum(self._frame_to_value(max_frame))

        self._ui.sp_value_from.setMinimum(self._frame_to_value(0))
        self._ui.sp_value_from.setMaximum(self._frame_to_value(max_frame))

        self._max_frame = max_frame

        self.blockSignals(False)

    # ----------------------------------------------------------------------
    def set_section(self, section):

        decimals = self._data_pool.get_axis_resolution(self._frame_viewer.current_file(), self._my_id)

        self._ui.sp_value_from.setDecimals(decimals)
        self._ui.sp_value_to.setDecimals(decimals)
        self._ui.sp_step.setDecimals(decimals)

        self._ui.sp_step.setMinimum(np.power(10., -decimals))

        if section['axis'] in ['X', 'Y']:
            self.switch_integration_mode(True)
        else:
            self.switch_integration_mode(section['integration'])

        self.limit_range = section['range_limit']
        self._ui.sp_step.setValue(section['step'])
        self._set_slider_value(section['min'], section['max'])
        self._set_spin_boxes(section['min'], section['max'])
        self._old_z_min = section['min']
        self._old_z_max = section['max']

    # ----------------------------------------------------------------------
    def get_values(self):
        return int(self._ui.sl_range.low()), int(self._ui.sl_range.high())

    # ----------------------------------------------------------------------
    def blockSignals(self, flag):
        self._ui.sl_frame.blockSignals(flag)
        self._ui.sl_range.blockSignals(flag)
        self._ui.sp_step.blockSignals(flag)
        self._ui.sp_value_from.blockSignals(flag)
        self._ui.sp_value_to.blockSignals(flag)


#-----------------------------------------------------------------------------
# ------------------------------------------------------- wortlabyrinth.py m n
# ------------------------------------------------------- erstellt: 01.02.2022
#-----------------------------------------------------------------------------
# Beschreibung: Ein Programm zum Erstellen von Wortlabyrinthen. Der Lösungsweg
# wird entlang eines Lösungssatzes gezeichnet. Das Labyrinth
import os
os.environ['PYGAME_HIDE_SUPPORT_PROMPT'] = "hide" # unterdrücke die pygame-Nachricht in der Konsole
import pygame
import random
import string # nötig für Buchstaben
string.ascii_letters # 'abcde....ABCDE...'
import sys
sys.setrecursionlimit(4000) # höhere Rekursionstiefe für komplexere Labyrinthe (>20x20)

m = int(sys.argv[1]) # Anzahl der ZEILEN
n = int(sys.argv[2]) # Anzahl der SPALTEN

if n < 3 or m < 3: # Fehlerbehandlung für zu kleine Matrix
	print('Error: n and m Dimensionen müssen mindestens 2x2 sein!')
	sys.exit()
if n > 40 or m > 40:  # Fehlerbehandlung für zu große Matrix
	print('Error: n and m Dimensionen dürfen nicht größer als 40 sein!')
	sys.exit()

satz = input('Gib deinen Lösungssatz ein:')
if len(satz) < n + m:
	print('FEHLER: Lösungssatz ist zu kurz für die gewünschte Labyrinthgröße.')
	sys.exit()

# Input String formatieren
satz = satz.upper() # alle Buchstaben in Grossbuchstaben
satz = ''.join(filter(str.isalnum, satz)) # Sonderzeichen entfernen
satz = ''.join([i for i in satz if not i.isdigit()]) # Zahlen entfernen
print('formatierter Lösungssatz:',satz, '| Buchstabenzahl:',len(satz))

BREITE = n * 50  # Bildschirmgröße in Pixel (angepasst auf Labyrinth-Maße)
HÖHE = m * 50
# BREITE = 1000  # Bildschirmgröße in Pixel (vordefinierte Maße)
# HÖHE = 1000

SPALTEN = n
ZEILEN = m
ZE_BH = BREITE // SPALTEN # Zellengröße (Breite-Höhe)

delta_linien = {'l':[(0,0),(0,ZE_BH)], 'r':[(ZE_BH,0),(ZE_BH,ZE_BH)], 'o':[(0,0),(ZE_BH,0)], 'u': [(0,ZE_BH),(ZE_BH,ZE_BH)]} # Wandpositionen (Linien)
delta_nachbarn = {'l':(-ZE_BH,0), 'r':(ZE_BH,0), 'o':(0,-ZE_BH), 'u':(0,ZE_BH)} # l = links, r = rechts, o = oben, u = unten
richtung_invers = {'l':'r', 'r':'l', 'o':'u', 'u':'o'} # gegenüberliegende Seiten

pygame.init() # Anfangsbildschirm initialisieren
screen = pygame.display.set_mode([BREITE,HÖHE])
farbe_hintergrund = pygame.Color('White')
farbe_linie = pygame.Color('Black')
# farbe_weg = pygame.Color('Green') # momentan nicht in Verwendung

def raster_erstellen():
	for i in range(SPALTEN*ZEILEN):
		pos = i % SPALTEN * ZE_BH, i // SPALTEN * ZE_BH
		raster[pos] = {b for b in 'lrou'} # 4 Zellwände an einer Raster-Position (set comprehension)

def add_pos(pos1,pos2):
	return pos1[0] + pos2[0], pos1[1] + pos2[1]

def zeichne_zelle(pos,wände):
	for wand in wände:
		delta_von, delta_bis = delta_linien[wand]
		von = add_pos(pos,delta_von)
		bis = add_pos(pos,delta_bis)
		pygame.draw.line(screen,farbe_linie,von,bis,4) # strichstärke 4

def nachbarn_ermitteln(pos):
	nachb = []
	for richtung, delta in delta_nachbarn.items():
		neue_pos = add_pos(pos,delta)
		if neue_pos not in raster: continue # schnappt sich die nächste Richtung
		nachb.append((richtung,neue_pos))
	random.shuffle(nachb)
	return nachb

def labyrinth_erstellen(pos_aktuell,richtung_von):
	besucht.add(pos_aktuell)
	raster[pos_aktuell].remove(richtung_von)
	nachb = nachbarn_ermitteln(pos_aktuell)
	for richtung_nach, pos_neu in nachb:
		if pos_neu in besucht: continue
		raster[pos_aktuell].remove(richtung_nach)
		labyrinth_erstellen(pos_neu, richtung_invers[richtung_nach]) # lösche die Wand aus der neuen Zelle in die ich reingehe

def mögliche_richtungen(pos):
	richtungen = []
	for richtung,delta in delta_nachbarn.items():
		neue_pos = add_pos(pos,delta)
		if neue_pos not in raster: continue
		if richtung in raster[pos]: continue
		richtungen.append(neue_pos)
	return richtungen

def labyrinth_lösen(pos_aktuell):
	besucht.append(pos_aktuell)
	if pos_aktuell == ziel:
		# print('aktuelle position ist das Ziel!',pos_aktuell,'Ziel:',ziel)
		return True
	for pos_neu in mögliche_richtungen(pos_aktuell):
		if pos_neu in besucht: continue
		if labyrinth_lösen(pos_neu):
			weg.append(pos_neu)
			# print('Weg nimmt zu:',weg) # überprüfen der Lösungs-Wegpukte
			return True

ziel = ((SPALTEN-1)*ZE_BH,(ZEILEN-1)*ZE_BH) # Koordinaten der Zielzelle
solution = False
iteration = 0

while solution == False:
	iteration += 1
	if iteration > 2000: # definiertes Limit für Anzahl an zufällig generierten Labyrinthen
		print('Keine Lösung nach 2000 Iterationen gefunden!')
		sys.exit()
	raster = {}
	raster_erstellen()
	weg = []
	besucht = set()
	labyrinth_erstellen((0,0),'l')
	besucht = []
	labyrinth_lösen((0,0))
	weg.append((0,0)) # Startposition hinzufügen
	if len(satz) == len(weg):
		solution = not solution # boolean auf 'True' setzen um 'while' Loop zu beenden
		print('Lösung nach',iteration,'Iterationen gefunden!')

screen.fill(farbe_hintergrund)
for pos,wände in raster.items():
	zeichne_zelle(pos,wände)
pygame.display.flip()
pygame.image.save(screen,'lab_1.png') # Screenshot 1 mit dem leeren Labyrinth

schrift = pygame.font.SysFont('Arial',40)
invers_satz = satz[::-1] # Inverse des Lösungssatzes

i = 0
while i < len(invers_satz):
	buchstabe = schrift.render(invers_satz[i], 1, (0,0,1), None) # Attribute: 'string', antialias, color, ?
	x,y = weg[i]
	x = x + ZE_BH // 3.5 # Buchstaben in mittlere Position innerhalb einer Zelle schieben
	screen.blit(buchstabe,(x,y)) # Buchstabe zeichnen
	pygame.display.flip()
	i += 1
pygame.image.save(screen,'lab_2.png') # Screenshot 2 mit dem Lösungsweg

for pos in raster.items(): # Zufällige Buchstaben in alle Felder setzen, die nicht Teil der Lösung sind
	if pos[0] not in weg:
		buchstabe = schrift.render(random.choice(string.ascii_letters).upper(), 1, (0,0,1), None) # Attribute: 'string', antialias, color, ?
		x,y = pos[0]
		x = x + ZE_BH // 3.5 # Buchstaben in mittlere Position innerhalb einer Zelle schieben
		screen.blit(buchstabe,(x,y)) # Buchstabe zeichnen
		pygame.display.flip()
pygame.image.save(screen,'lab_3.png') # Screenshot 3 mit dem kompletten Labyrinth

pygame.quit()


from typing import Tuple

import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.distributions import RelaxedOneHotCategorical

class Codebook(torch.nn.Embedding):
    """VQ-VAE Codebook.

    Implements the algorithm presented in "Neural Discrete Representation Learning"
    [https://arxiv.org/abs/1711.00937], corresponding to the distribution:

    ..math
        q_\varphi(z_q = e_k | z_e) = \delta_k ( \argmin_{l=1}^K \| z_e - e_l \| )
        \quad \forall 1 \leq k \leq K

    This module stores a finite set of codebooks, used to compute quantization of input tensors.

    Note
    ----
    This module inherits from the `torch.nn.Embedding` module, and uses its
    forward implementation for the `codebook_lookup` function.

    Parameters
    ----------
    num_codebook: number of codebooks.
    dim_codebook: dimension of codebooks.
    """

    def __init__(self, num_codebook: int, dim_codebook: int, **kwargs):
        super().__init__(num_embeddings=num_codebook, embedding_dim=dim_codebook)
        self.weight.data.uniform_(-1 / num_codebook, 1 / num_codebook)
        self._eps = torch.finfo(torch.float32).eps

    def quantize(self, encoding: torch.Tensor) -> Tuple[torch.Tensor, dict]:
        """Quantize an encoding vector with respect to the codebook.

        Compute the distances between the encoding and the codebook vectors, and assign
        the closest codebook to each point in the feature map. The gradient from the
        return quantized tensor is copied to the encoding. This function also returns
        the latent loss and the normalized perplexity.

        Parameters
        ----------
        encoding: input tensor with shape `(*, D)`.

        Returns
        -------
        quantized: quantized tensor with the same shape as the input vector.
        indices: indices sampled with shape `(*, K)`.
        losses: dictionary containing the latent loss between encodings and selected
        codebooks, and the normalized perplexity.
        """
        distances = self.compute_distances(encoding)
        indices = torch.argmin(distances, dim=-1)
        quantized = self.codebook_lookup(indices)
        loss_latent = F.mse_loss(encoding, quantized)
        quantized = encoding + (quantized - encoding).detach()
        # Compute perplexity
        indices_onehot = F.one_hot(indices, num_classes=self.num_codebook)
        probs = (
            indices_onehot
            .view(-1, self.num_codebook)
            .float()
            .mean(dim=0)
        )
        perplexity = torch.exp(-torch.sum(probs * torch.log(probs + self._eps)))
        perplexity = perplexity / self.num_codebook
        return quantized, indices_onehot, {
            "loss_latent": loss_latent,
            "perplexity": perplexity,
        }

    def compute_distances(self, encodings: torch.Tensor) -> torch.Tensor:
        """Compute distance between encodings and codebooks.

        Parameters
        ----------
        encodings: encoding tensor with shape `(*, D)`.

        Returns
        -------
        Distances with shape `(*, K)`.
        """
        distances = (encodings.unsqueeze(-2) - self.weight).square().sum(-1)
        return distances

    def codebook_lookup(self, indices: torch.Tensor) -> torch.Tensor:
        """Associate tensor of indices with the corresponding codebooks.

        Parameters
        ----------
        indices: Discrete vector with shape (*).

        Returns
        -------
        Tensor of codebooks with shape `(*, D)`.
        """
        return self(indices)

    @property
    def num_codebook(self):
        return self.num_embeddings

    @property
    def dim_codebook(self):
        return self.embedding_dim

class EMACodebook(Codebook):
    """VQ-VAE Codebook with Exponential Moving Avergage (EMA).

    This modules is similar to the `Codebook` module, with the addition of updating
    codebook positions with EMA.

    Parameters
    ----------
    num_codebook: number of codebooks.
    dim_codebook: dimension of codebooks.
    gamma: degree of weighting decrease. Must be between `0` and `1`. Default is `0.99`.
    epsilon: pseudocount for the Laplace smoothing. Default is `1e-5`.
    """

    def __init__(
        self,
        num_codebook: int,
        dim_codebook: int,
        gamma: float = 0.99,
        epsilon: float = 1e-5,
        **kwargs
    ):
        super().__init__(num_codebook=num_codebook, dim_codebook=dim_codebook, **kwargs)
        self.gamma = gamma
        self.epsilon = epsilon
        self.register_buffer("ema_cluster_size", torch.zeros(self.num_codebook))
        self.register_buffer("ema_positions", self.weight.clone())

    def quantize(self, encoding: torch.Tensor) -> Tuple[torch.Tensor, dict]:
        """Quantize an encoding vector with respect to the codebook.

        Note
        ----
        Because codebooks are updated using EMA, the latent loss does not back propagate
        gradient toward the codebooks.

        Parameters
        ----------
        encoding: input tensor with shape `(*, D)`.

        Returns
        -------
        quantized: quantized tensor with the same shape as the input vector.
        indices: indices sampled with shape `(*, K)`.
        losses: dictionary containing the latent loss between encodings and selected
        codebooks, and the normalized perplexity.
        """
        encoding_flatten = encoding.reshape(-1, self.dim_codebook)
        distances = self.compute_distances(encoding_flatten)
        indices = torch.argmin(distances, dim=-1)
        indices_onehot = F.one_hot(indices, num_classes=self.num_codebook)
        probs = indices_onehot.float().mean(dim=0)
        perplexity = torch.exp(-torch.sum(probs * torch.log(probs + self._eps)))
        perplexity = perplexity / self.num_codebook
        if self.training:
            # Update cluster size
            instant_cluster_size = indices_onehot.sum(dim=0)
            self.ema_cluster_size = (
                self.gamma * self.ema_cluster_size
                + (1 - self.gamma) * instant_cluster_size
            )
            total_cluster_size = self.ema_cluster_size.sum(dim=-1, keepdims=True)
            # Laplace smoothing to avoid empty clusters
            self.ema_cluster_size = (
                (self.ema_cluster_size + self.epsilon)
                / (total_cluster_size + self.num_codebook * self.epsilon)
                * total_cluster_size
            )
            # Update positions
            instant_positions = indices_onehot.T.float() @ encoding_flatten
            self.ema_positions = (
                self.gamma * self.ema_positions + (1 - self.gamma) * instant_positions
            )
            # Update codebook
            self.weight.data = self.ema_positions / self.ema_cluster_size.unsqueeze(-1)
        quantized = self.codebook_lookup(indices).view(encoding.shape)
        loss_latent = F.mse_loss(encoding, quantized)
        quantized = encoding + (quantized - encoding).detach()
        return quantized, indices_onehot, {
            "loss_latent": loss_latent,
            "perplexity": perplexity,
        }

class GumbelCodebook(Codebook):
    """VQ-VAE Codebook with Gumbel Sotmax Reparametrization.

    Parameters
    ----------
    num_codebook: number of codebooks.
    dim_codebook: dimension of codebooks.
    tau: temperature for the Gumbel Softmax distribution. Default is `0.5`.
    """

    def __init__(self, num_codebook: int, dim_codebook: int, tau: float = 0.5):
        super().__init__(num_codebook=num_codebook, dim_codebook=dim_codebook)
        self.tau = tau

    def quantize(self, encoding: torch.Tensor) -> Tuple[torch.Tensor, dict]:
        """Quantize the encoded vector using Gumbel Softmax.

        Implements a quantization based on the distribution:
        ..math
            q_\varphi(z_q = e_k | z_e) = \frac{\exp{ - \| z_e - e_k \|^2 }}
            {\sum_{l=1}^K \exp { - \| z_e - e_l \|^2 }}
            \quad \forall 1 \leq k \leq K

        Note
        ----
        During training, we sample instead from the associated Gumbel Softmax
        distribution in order to allow gradient propagation.

        Returns
        -------
        quantized: quantized tensor with the same shape as the input vector.
        losses: dictionary containing the latent loss between encodings and selected
        codebooks, and the normalized perplexity.
        """
        encoding_flatten = encoding.reshape(-1, self.dim_codebook)
        distances = self.compute_distances(encoding_flatten)
        gumbel_softmax = RelaxedOneHotCategorical(
            logits=-distances, temperature=self.tau
        )
        indices = gumbel_softmax.rsample()
        if self.training:
            quantized = indices @ self.weight
        else:
            quantized = self.codebook_lookup(indices.argmax(-1))
        # KL divergence
        kl = gumbel_softmax.probs * torch.log(
            gumbel_softmax.probs * self.num_codebook + self._eps
        )
        kl = kl.sum()
        # Compute perplexity
        probs = indices.mean(dim=0)
        perplexity = torch.exp(-torch.sum(probs * torch.log(probs + self._eps)))
        perplexity = perplexity / self.num_codebook
        quantized = quantized.view(encoding.shape)
        indices = indices.view(*encoding.shape[:-1], -1)
        return quantized, indices, {"loss_latent": kl, "perplexity": perplexity}


from __future__ import annotations

import re
import urllib.parse
from functools import partial
from typing import Any

from bs4 import BeautifulSoup
from markdownify import MarkdownConverter  # type: ignore
import aiohttp

try:
    import lxml  # type: ignore

    HTML_PARSER = "lxml"
except ImportError:
    HTML_PARSER = "html.parser"

class DocMarkdownConverter(MarkdownConverter):
    def convert_pre(self, el, text):
        """Wrap any codeblocks in `py` for syntax highlighting."""
        code = "".join(el.strings)

        return f"```py\n{code}```"

def markdownify(html: Any):
    return DocMarkdownConverter(bullets="\N{BULLET}").convert(html)

async def get_url_response(url: str):
    async with aiohttp.ClientSession() as session:
        return await session.get(url)

async def _process_mozilla_doc(url: str):
    """
    From a given url from developers.mozilla.org, processes format,
    returns tag formatted content
    """
    response = await get_url_response(url)
    if response.status == 404:
        return "No results"
    if response.status != 200:
        return f"An error occurred (status code: {response.status}). Retry later."

    body = BeautifulSoup(await response.text(), HTML_PARSER).find("body")

    contents = body.find(id="wikiArticle").find(lambda x: x.name == "p" and x.text)
    return markdownify(contents).replace(
        "(/en-US/docs", "(https://developer.mozilla.org/en-US/docs"
    )

async def html_ref(text: str):
    """Displays informations on an HTML tag"""
    text = text.strip("<>`")

    base_url = f"https://developer.mozilla.org/en-US/docs/Web/HTML/Element/{text}"
    url = urllib.parse.quote_plus(base_url, safe=";/?:@&=$,><-[]")

    output = await _process_mozilla_doc(url)
    if not isinstance(output, str):
        # Error message already sent
        return

    return output

async def _http_ref(part, text: str):
    """Displays informations about HTTP protocol"""
    base_url = f"https://developer.mozilla.org/en-US/docs/Web/HTTP/{part}/{text}"
    url = urllib.parse.quote_plus(base_url, safe=";/?:@&=$,><-[]")

    output = await _process_mozilla_doc(url)
    if not isinstance(output, str):
        # Error message already sent
        return

    return output

http_headers = partial(_http_ref, "Headers")
http_methods = partial(_http_ref, "Methods")
http_status = partial(_http_ref, "Status")
csp_directives = partial(_http_ref, "Headers/Content-Security-Policy")

async def _git_main_ref(part: str, text: str):
    """Displays a git help page"""
    text = text.strip("`")

    if part and text == "git":
        # just 'git'
        part = ""
    if not part and not text.startswith("git"):
        # gittutorial, giteveryday...
        part = "git"
    base_url = f"https://git-scm.com/docs/{part}{text}"
    url = urllib.parse.quote_plus(base_url, safe=";/?:@&=$,><-[]")

    response = await get_url_response(url)
    if response.status != 200:
        return f"An error occurred (status code: {response.status}). Retry later."
    if str(response.url) == "https://git-scm.com/docs":
        # Website redirects to home page
        return "No results"

    soup = BeautifulSoup(await response.text(), HTML_PARSER)
    sectors = soup.find_all("div", {"class": "sect1"}, limit=3)

    title = sectors[0].find("p").text

    ls = []
    for tag in sectors[1:]:
        content = "\n".join(
            [markdownify(p) for p in tag.find_all(lambda x: x.name in ["p", "pre"])]
        )
        ls.append(content)

    return f"**{title}**\n\n" + "\n\n".join(ls)

git_ref = partial(_git_main_ref, "git-")
git_tutorial_ref = partial(_git_main_ref, "")

async def sql_ref(text: str):
    """Displays reference on an SQL statement"""
    text = text.strip("`").lower()
    if text in {"check", "unique", "not null"}:
        text += " constraint"
    text = re.sub(" ", "-", text)
    base_url = f"http://www.sqltutorial.org/sql-{text}/"
    url = urllib.parse.quote_plus(base_url, safe=";/?:@&=$,><-[]")
    response = await get_url_response(url)
    if response.status != 200:
        return f"An error occurred (status code: {response.status}). Retry later."

    body = BeautifulSoup(await response.text(), HTML_PARSER).find("body")
    intro = body.find(lambda x: x.name == "h2" and "Introduction to " in x.string)
    title = body.find("h1").string
    ps = []
    for tag in tuple(intro.next_siblings):
        if tag.name == "h2" and tag.text.startswith("SQL "):
            break
        if tag.name == "p":
            ps.append(tag)
    description = "\n".join([markdownify(p) for p in ps])[:2048]
    return f"**{title}**\n\n{description}"

async def haskell_ref(text: str):
    """Displays informations on given Haskell topic"""
    text = text.strip("`")

    snake = "_".join(text.split(" "))

    base_url = f"https://wiki.haskell.org/{snake}"
    url = urllib.parse.quote_plus(base_url, safe=";/?:@&=$,><-[]")

    response = await get_url_response(url)
    if response.status == 404:
        return f"No results for `{text}`"
    if response.status != 200:
        return f"An error occurred (status code: {response.status}). Retry later."

    soup = BeautifulSoup(await response.text(), HTML_PARSER).find("div", id="content")

    title = soup.find("h1", id="firstHeading").string
    description = "\n".join(
        [
            markdownify(p)
            for p in soup.find_all(
                lambda x: x.name in ["p", "li"]
                and tuple(x.parents)[1].name not in ("td", "li"),
                limit=6,
            )
        ]
    )[:2048]

    return f"**{title}**\n\n{description}"


# Define here the models for your spider middleware
#
# See documentation in:
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
import random

from scrapy import signals
import pymysql

# useful for handling different item types with a single interface
from itemadapter import is_item, ItemAdapter

class BossProDownloaderMiddleware:
    # Not all methods need to be defined. If a method is not defined,
    # scrapy acts as if the downloader middleware does not modify the
    # passed objects.
    proxy_https = [
      "111.225.153.35:8089",
    ]
    user_agent_list = [
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1",
        "Mozilla/5.0 (X11; CrOS i686 2268.111.0) AppleWebKit/536.11 "
        "(KHTML, like Gecko) Chrome/20.0.1132.57 Safari/536.11",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1092.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.6 "
        "(KHTML, like Gecko) Chrome/20.0.1090.0 Safari/536.6",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/537.1 "
        "(KHTML, like Gecko) Chrome/19.77.34.5 Safari/537.1",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.9 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.0) AppleWebKit/536.5 "
        "(KHTML, like Gecko) Chrome/19.0.1084.36 Safari/536.5",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 5.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_0) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1063.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1062.0 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.1) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.1 Safari/536.3",
        "Mozilla/5.0 (Windows NT 6.2) AppleWebKit/536.3 "
        "(KHTML, like Gecko) Chrome/19.0.1061.0 Safari/536.3",
        "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24",
        "Mozilla/5.0 (Windows NT 6.2; WOW64) AppleWebKit/535.24 "
        "(KHTML, like Gecko) Chrome/19.0.1055.1 Safari/535.24"
    ]
    @classmethod
    def from_crawler(cls, crawler):
        # This method is used by Scrapy to create your spiders.
        s = cls()
        crawler.signals.connect(s.spider_opened, signal=signals.spider_opened)
        return s

    #用来拦截请求
    def process_request(self, request, spider):
        # UA 伪装
        request.headers['User-Agent'] = random.choice(self.user_agent_list)
        if request.url.split(":")[0] == 'https':
            request.meta['proxy'] = "https://" + random.choice(self.proxy_https)
        return None

    #拦截响应
    def process_response(self, request, response, spider):
        # Called with the response returned from the downloader.

        return response

    def process_exception(self, request, exception, spider):
        if request.url.split(":")[0] == 'https':
            request.meta['proxy'] = "https://" + random.choice(self.proxy_https)
        return request
    def spider_opened(self, spider):
        spider.logger.info('Spider opened: %s' % spider.name)


from parse import convert_to_graph, get_info
from rotation import randAngle, shuffleEdges, angleConvert, rotateGraph
from restore import restore
from plot import plot_graph_3d as plot

MIN_ANGLE = 0.0001745329252

if __name__ == '__main__':
    name = 'Antazoline'
    raw = get_info(name)
    graph = convert_to_graph(raw)
    graph.find_good_edges()

    vertices = graph.get_vertices()
    edges, vertex = graph.select_edges()
    edges = shuffleEdges(edges)
    angles = randAngle(len(edges))[1]
    rotated = rotateGraph(vertices, edges, angles, graph.bonds)

    res = restore(vertices, rotated, graph.bonds, vertex)
    restored = res[2]

    # Results output
    result = ''
    result += 'Vertex coordinates:\n'
    result +='{:31} {:37} {:30}\n'.format("INITIAL", "ROTATED", "RESTORED")
    for i in range(len(vertices)):
        v, n, r = vertices[i], map(lambda x: round(x, 6), rotated[i]), map(lambda x: round(x, 6), restored[i])
        result +='({:-8} {:-8} {:-8}) -> ({:-10} {:-10} {:-10}) -> ({:-10} {:-10} {:-10})\n'.format(*v, *n, *r)

    result += '\nInitial rotation angles:\n'
    result += '{:11} {:8} {:8}\n'.format("EDGE", "DEG", "RAD")
    for i in range(len(edges)):
        rad_angle, angle = angleConvert(angles[i], True, False), angleConvert(angles[i])
        result += '{:8} {:8}° {:10}rad\n'.format(str(edges[i]), angle, rad_angle)

    result += '\nRestored angles\n'
    result += '{:11} {:9} {:8}\n'.format("EDGE", "DEG", "RAD")
    for pair in zip(res[0], res[1]):
        if abs(pair[1]) < MIN_ANGLE: continue
        rad_angle, angle = angleConvert(pair[1], True, False), angleConvert(pair[1])
        result += '{:8} {:8}° {:10}rad\n'.format(str(pair[0]), angle, rad_angle)

    print(result)
    f = open("result/result.txt", "w")
    f.write(result)
    f.close()

    plot(graph, "init")
    graph.set_vertices(rotated)
    plot(graph, "rotated")
    graph.set_vertices(restored)
    plot(graph, "restored")


import main
import matplotlib.pyplot as plt
import numpy as np
angles = []
dtheta = 3.14 / 100
theta = 0
F = np.zeros(int(main.T / main.dt) * 2 + 2)
modules = np.zeros(int(main.T / main.dt) + 1)
for i in range(0, int(main.T / main.dt)):
    if i<main.T/main.dt/6:
        F[2 * i], F[2 * i + 1] = np.cos(0), np.sin(0)
    else:
        F[2 * i], F[2 * i + 1] = np.cos(np.pi/2), np.sin(np.pi/2)
    modules[i] = np.linalg.norm([F[2 * i], F[2 * i + 1]])
F = F * main.g * main.isp * main.fuel / main.T
X = []
Y = []
for i in range(0,200):
    X.append(main.R * np.cos(theta))
    Y.append(main.R * np.sin(theta))
    theta += dtheta
u = main.values(F)
x = u[0]
y = u[1]
m = u[4]
t = np.linspace(0, main.T, np.size(m))
#plt.plot(t, m)
plt.plot(x, y)
plt.plot(X, Y)
plt.xlim(-10 * main.R, 10 * main.R)
plt.ylim(-10 * main.R, 10 * main.R)
plt.show()
print(main.q(main.final_values(F)))