simple_graph_execution_state

本文介绍TensorFlow中SimpleGraphExecutionState类的功能和实现细节,包括如何从原始GraphDef生成可执行的SimpleClientGraph,以及如何管理和维护状态节点的位置。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

simple_graph_execution_state.h

#ifndef TENSORFLOW_CORE_COMMON_RUNTIME_SIMPLE_GRAPH_EXECUTION_STATE_H_
#define TENSORFLOW_CORE_COMMON_RUNTIME_SIMPLE_GRAPH_EXECUTION_STATE_H_

#include <functional>
#include <memory>
#include <string>
#include <vector>

#include "tensorflow/core/common_runtime/build_graph_options.h"
#include "tensorflow/core/common_runtime/device.h"
#include "tensorflow/core/common_runtime/device_set.h"
#include "tensorflow/core/framework/graph.pb.h"
#include "tensorflow/core/framework/step_stats.pb.h"
#include "tensorflow/core/graph/costmodel.h"
#include "tensorflow/core/graph/graph.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/platform/macros.h"
#include "tensorflow/core/platform/mutex.h"
#include "tensorflow/core/platform/thread_annotations.h"
#include "tensorflow/core/platform/types.h"

namespace tensorflow {
struct SessionOptions;
class StepStats;
class Timeline;

namespace subgraph {
struct RewriteGraphMetadata;
}

struct SimpleGraphExecutionStateOptions {
  const DeviceSet* device_set = nullptr;
  const SessionOptions* session_options = nullptr;
  // A map from node name to device name, representing the unchangeable
  // placement of stateful nodes.
  // 从节点名称到设备名称的映射,表示状态节点不可更改的位置。
  std::unordered_map<string, string> stateful_placements;
};

// A SimpleClientGraph is simply a sub-graph of the full graph as induced by BuildGraphOptions.
// SimpleClientGraph 只是 BuildGraphOptions 引发的完整图形的子图。
struct SimpleClientGraph {
  explicit SimpleClientGraph(std::unique_ptr<FunctionLibraryDefinition> flib,
                             DataTypeVector feed_types,
                             DataTypeVector fetch_types)
      : flib_def(std::move(flib)),
        graph(flib_def.get()),
        feed_types(std::move(feed_types)),
        fetch_types(std::move(fetch_types)) {}
  // Each client-graph gets its own function library since optimization passes
  // post rewrite for execution might want to introduce new functions.
  // 每个客户端图获得自己的函数库,因为优化通过后重写以执行可能需要引入新的函数。
  std::unique_ptr<FunctionLibraryDefinition> flib_def;
  Graph graph;
  DataTypeVector feed_types;
  DataTypeVector fetch_types;
};

// SimpleGraphExecutionState is responsible for generating an
// executable SimpleClientGraph from the original GraphDef that specifies
// the complete graph and from BuildGraphOptions which specifies
// input/output nodes.
// SimpleGraphExecutionState 负责从原始的 GraphDef 中生成一个可执行的 SimpleClientGraph,
// 该 GraphDef 指定了完整的图形,并且指定了指定输入/输出节点的 BuildGraphOptions。
//
// An executable Graph differs from a GraphDef by being Placed,
// meaning that each Node is assigned to a single Device in the
// available set.
// 可执行图形与 GraphDef 的不同之处在于放置,这意味着每个节点都分配给可用集合中的单个设备。
//
// When SimpleGraphExecutionState is first constructed it instantiates
// a full Graph from the provided GraphDef, and places it, using only
// the static device assignments from the GraphDef.  Nodes without are
// currently placed in a very naive way.  Since stateful Nodes cannot
// be moved after initial placement, it is important that stateful
// Nodes get sensible initial device assignments in the graph
// definition.
// 当 SimpleGraphExecutionState 首次构建时,它将从提供的 GraphDef 中实例化一个完整的图形,并将其放置,
// 仅使用 GraphDef 中的静态设备分配。没有的节点目前处于非常幼稚的方式。
// 由于有状态节点在初始放置之后无法移动,因此状态很重要。节点在图形定义中获得明智的初始设备分配。
//
// Subsequently, SimpleGraphExecutionState generates a SimpleClientGraph on
// demand, which is a sub-graph of the latest placement of the full
// Graph.  MasterSession uses such a SimpleClientGraph to execute one or
// more similar client requests.
// 随后,SimpleGraphExecutionState 根据需要生成一个 SimpleClientGraph,它是完整图形的最新位置的子图。 
// MasterSession 使用这样的 SimpleClientGraph 来执行一个或多个类似的客户端请求。
//
// SimpleGraphExecutionState is thread-safe.
// SimpleGraphExecutionState 是线程安全的。
class SimpleGraphExecutionState {
 public:
  virtual ~SimpleGraphExecutionState();

  // Creates a new `SimpleGraphExecutionState` for the given
  // `graph_def`, which represents the entire graph for a session.
  // 为给定的`graph_def`创建一个新的`SimpleGraphExecutionState`,它表示会话的整个图形。
  
  // N.B. This method uses `GraphDef::Swap()` and leaves `graph_def`
  // in an undefined state. If it is necessary to use `*graph_def`
  // after this call, make an explicit copy of the graph before
  // calling this method.
  // 该方法使用`GraphDef::Swap()`,并将 `graph_def` 留在未定义的状态。 
  // 如果在此调用后需要使用`*graph_def`,则在调用此方法之前,请先创建图形的显式副本。
  static Status MakeForBaseGraph(
      GraphDef* graph_def, const SimpleGraphExecutionStateOptions& options,
      std::unique_ptr<SimpleGraphExecutionState>* out_state);

  // Creates a new `SimpleGraphExecutionState` and `SimpleClientGraph`
  // for the subgraph of `original_graph_def` defined by `subgraph_options`.
  // 为`subgraph_options`定义的`original_graph_def`子图创建一个新的`SimpleGraphExecutionState`和`SimpleClientGraph`。
  static Status MakeForPrunedGraph(
      const FunctionDefLibrary& func_def_lib,
      const SimpleGraphExecutionStateOptions& options,
      const GraphDef& original_graph_def,
      const BuildGraphOptions& subgraph_options,
      std::unique_ptr<SimpleGraphExecutionState>* out_state,
      std::unique_ptr<SimpleClientGraph>* out_client_graph);

  // Creates a new SimpleGraphExecutionState representing the
  // concatenation of this graph, and the graph defined by
  // "extension_def". The same name may not be used to define a node
  // in both this graph and "extension_def".
  // 创建一个新的 SimpleGraphExecutionState,表示此图形的连接,以及由 "xtension_def" 定义的图形。
  // 同一个名称可能不用于在此图和"extension_def"中定义节点。
  //
  // If successful, returns OK and the caller takes ownership of "*out".
  // Otherwise returns an error and does not modify "*out".
  // 如果成功,返回 OK,调用者拥有"*out"的所有权。 否则返回错误,不会修改"*out"。
  //
  // After calling `old_state->Extend()`, `old_state` may no longer be used.
  // 调用 `old_state->Extend()`后,可能不再使用`old_state`。
  //
  // NOTE(mrry): This method respects the placement of stateful nodes in
  // in *this, but currently does not transfer any other placement
  // or cost model information to the new graph.
  // 此方法尊重在 *this 中的状态节点的位置,但是目前不会将任何其他位置或成本模型信息传输到新的图形。
  Status Extend(const GraphDef& extension_def,
                std::unique_ptr<SimpleGraphExecutionState>* out) const;

  // Builds a SimpleClientGraph (a sub-graph of the full graph as induced by
  // the Node set specified in "options").  If successful, returns OK
  // and the caller takes the ownership of "*out". Otherwise, returns an error.
  // 构建 SimpleClientGraph(由"options"中指定的 Node 集合引发的完整图形的子图)。 
  // 如果成功,返回OK,调用者拥有"*out"的所有权。 否则返回错误。
  Status BuildGraph(const BuildGraphOptions& options,
                    std::unique_ptr<SimpleClientGraph>* out);

  // The graph returned by BuildGraph may contain only the pruned
  // graph, whereas some clients may want access to the full graph.
  // BuildGraph 返回的图可能只包含已修剪的图形,而某些客户端可能希望访问完整的图形。
  const Graph* full_graph() {
    return graph_;
  }

  // Returns the node with the given name, or null if it does not exist.
  // 返回具有给定名称的节点,如果不存在则返回 null。
  const Node* get_node_by_name(const string& name) const {
    NodeNameToCostIdMap::const_iterator iter =
        node_name_to_cost_id_map_.find(name);
    if (iter != node_name_to_cost_id_map_.end()) {
      return graph_->FindNodeId(iter->second);
    } else {
      return nullptr;
    }
  }

  // Returns a reference to the current graph_def.  Use must
  // not extend beyond lifetime of SimpleGrahExecutionState object.
  // 返回对当前 graph_def 的引用。 使用不能超出 SimpleGrahExecutionState 对象的生命周期。
  const GraphDef& original_graph_def() { return original_graph_def_; }

  // Returns the map of stateful placements as a map of node name to placement string.
  // 将状态展示位置的地图作为节点名称的映射返回到展示位置字符串。
  std::unordered_map<string, string> GetStatefulPlacements() const {
    mutex_lock l(mu_);
    return stateful_placements_;
  }

 private:
  SimpleGraphExecutionState(GraphDef* graph_def,
                            const SimpleGraphExecutionStateOptions& options);

  Status InitBaseGraph(const BuildGraphOptions& options);

  // Map of placed stateful nodes, i.e. nodes for which is_stateful()
  // is true, such as "params" and "queue" nodes.  Once placed these
  // nodes can not be moved to a different device.  Maps node names to
  // device names.
  // 放置状态节点的映射,即 is_stateful() 为 true 的节点,如"params"和"queue"节点。
  // 一旦放置这些节点不能移动到不同的设备。 将节点名称映射到设备名称。
  std::unordered_map<string, string> stateful_placements_;  // Immutable after ctor.
                                                            
  void SaveStatefulNodes(Graph* graph);
  void RestoreStatefulNodes(Graph* graph);

  GraphDef original_graph_def_;            // Immutable after ctor.
  const DeviceSet* device_set_;            // Not owned
  const SessionOptions* session_options_;  // Not owned

  mutable mutex mu_;
  CostModel costs_ GUARDED_BY(mu_);

  // Map from name to Node for the full graph in placed_.
  NodeNameToCostIdMap node_name_to_cost_id_map_;

  // 'flib_def_' is initialized from the initial graph def's library,
  // and may be updated by a graph optimization pass.
  // 'flib_def_'初始化从初始图形 def 的库,可以通过图优化通过来更新。
  std::unique_ptr<FunctionLibraryDefinition> flib_def_;

  // `rewrite_metadata_` is only set for SimpleGraphExecutionState
  // objects created by `MakeForPrunedGraph()`.
  // `rewrite_metadata_`仅为由`MakeForPrunedGraph()`创建的 SimpleGraphExecutionState 对象设置。
  std::unique_ptr<subgraph::RewriteGraphMetadata> rewrite_metadata_;

  // The dataflow graph owned by this object. 该对象拥有的数据流图。
  Graph* graph_;

  TF_DISALLOW_COPY_AND_ASSIGN(SimpleGraphExecutionState);
};

}  // namespace tensorflow

#endif  // TENSORFLOW_CORE_COMMON_RUNTIME_SIMPLE_GRAPH_EXECUTION_STATE_H_





simple_graph_execution_state.cc


#include "tensorflow/core/common_runtime/simple_graph_execution_state.h"


#include <memory>
#include <string>
#include <unordered_set>
#include <vector>


#include "tensorflow/core/common_runtime/device.h"
#include "tensorflow/core/common_runtime/optimization_registry.h"
#include "tensorflow/core/common_runtime/simple_placer.h"
#include "tensorflow/core/framework/graph.pb_text.h"
#include "tensorflow/core/framework/graph_def_util.h"
#include "tensorflow/core/graph/graph.h"
#include "tensorflow/core/graph/graph_constructor.h"
#include "tensorflow/core/graph/subgraph.h"
#include "tensorflow/core/graph/validate.h"
#include "tensorflow/core/lib/core/errors.h"
#include "tensorflow/core/lib/core/status.h"
#include "tensorflow/core/lib/strings/stringprintf.h"
#include "tensorflow/core/platform/logging.h"
#include "tensorflow/core/platform/mutex.h"
#include "tensorflow/core/platform/types.h"
#include "tensorflow/core/util/util.h"


#ifndef IS_MOBILE_PLATFORM
#include "tensorflow/core/grappler/clusters/utils.h"
#include "tensorflow/core/grappler/clusters/virtual_cluster.h"
#include "tensorflow/core/grappler/grappler_item.h"
#include "tensorflow/core/grappler/optimizers/meta_optimizer.h"
#endif  // IS_MOBILE_PLATFORM


namespace tensorflow {


SimpleGraphExecutionState::SimpleGraphExecutionState(
    GraphDef* graph_def, const SimpleGraphExecutionStateOptions& options)
    : stateful_placements_(options.stateful_placements),
      device_set_(options.device_set),
      session_options_(options.session_options),
      costs_(true /*is_global*/),
      flib_def_(new FunctionLibraryDefinition(OpRegistry::Global(),
                                              graph_def->library())),
      graph_(nullptr) {
  // NOTE(mrry): GraphDef does not have a move constructor, so we pass
  // a non-const pointer and use `Swap()` to transfer the contents without copying.
  // GraphDef 没有移动构造函数,所以我们传递一个 non-const 指针,并使用`Swap()`来传输内容而不复制。
  original_graph_def_.Swap(graph_def);
  // TODO(mrry): Publish placement visualizations or handle the log placement option.
  // 发布位置可视化或处理日志位置选项。
}


SimpleGraphExecutionState::~SimpleGraphExecutionState() {
  node_name_to_cost_id_map_.clear();
  delete graph_;
}


/* static */ Status SimpleGraphExecutionState::MakeForBaseGraph(
    GraphDef* graph_def, const SimpleGraphExecutionStateOptions& options,
    std::unique_ptr<SimpleGraphExecutionState>* out_state) {
  std::unique_ptr<SimpleGraphExecutionState> ret(
      new SimpleGraphExecutionState(graph_def, options));


  TF_RETURN_IF_ERROR(AddDefaultAttrsToGraphDef(&ret->original_graph_def_,
                                               *ret->flib_def_.get(), 0));
  // TODO(mrry): Refactor InitBaseGraph() so that we don't have to
  // pass an empty BuildGraphOptions (that isn't going to be used when
  // place_pruned_graph is false).
  // 重构 InitBaseGraph() 使我们不必传递一个空的 BuildGraphOptions(当 place_pruned_graph 为 false 时不会被使用)。
  if (!ret->session_options_->config.graph_options().place_pruned_graph()) {
    TF_RETURN_IF_ERROR(ret->InitBaseGraph(BuildGraphOptions()));
  }
  *out_state = std::move(ret);
  return Status::OK();
}


/* static */ Status SimpleGraphExecutionState::MakeForPrunedGraph(
    const FunctionDefLibrary& func_def_lib,
    const SimpleGraphExecutionStateOptions& options, const GraphDef& graph_def,
    const BuildGraphOptions& subgraph_options,
    std::unique_ptr<SimpleGraphExecutionState>* out_state,
    std::unique_ptr<SimpleClientGraph>* out_client_graph) {
  DCHECK(options.session_options->config.graph_options().place_pruned_graph());
  // NOTE(mrry): This makes a copy of `graph_def`, which is
  // regrettable. We could make `GraphDef` objects sharable between
  // execution states to optimize pruned graph execution, but since
  // this case is primarily used for interactive sessions, we make the
  // bet that graph construction is not performance-critical. (Note
  // also that the previous version used `Extend()`, which is strictly
  // more expensive than copying a `GraphDef`.)
  // 这是一个 `graph_def` 的副本,这是令人遗憾的。
  // 我们可以使 `GraphDef` 对象在执行状态之间共享,以优化修剪图执行,但是由于这种情况主要用于交互式会话,所以我们打赌图形构造不是性能关键。
  //(另请注意,以前的版本使用`Extend()`,这比复制 `GraphDef` 要严格得多)
  GraphDef temp(graph_def);
  std::unique_ptr<SimpleGraphExecutionState> ret(
      new SimpleGraphExecutionState(&temp, options));
  TF_RETURN_IF_ERROR(AddDefaultAttrsToGraphDef(&ret->original_graph_def_,
                                               *ret->flib_def_.get(), 0));
  TF_RETURN_IF_ERROR(ret->InitBaseGraph(subgraph_options));
  TF_RETURN_IF_ERROR(ret->BuildGraph(subgraph_options, out_client_graph));
  *out_state = std::move(ret);
  return Status::OK();
}


Status SimpleGraphExecutionState::Extend(
    const GraphDef& extension_def,
    std::unique_ptr<SimpleGraphExecutionState>* out) const {
  std::unordered_set<string> new_names;
  // 1. Build an index of the new node names.
  // 1. 构建新节点名称的索引。
  for (const NodeDef& node : extension_def.node()) {
    new_names.insert(node.name());
  }


  // 2. Add the non-duplicates from the old graph to the new graph.
  //    Return an error if the same node name appears in both the
  //    old graph and the extension.
  // 2. 将不重复的旧图形添加到新图形。如果同一个节点名称出现在旧图形和扩展名中,则返回错误。
  GraphDef gdef;
  for (const NodeDef& node : original_graph_def_.node()) {
    if (new_names.count(node.name()) == 0) {
      *gdef.add_node() = node;
    } else {
      return errors::InvalidArgument(tensorflow::strings::Printf(
          "GraphDef argument to Extend includes node '%s', which was created "
          "by a previous call to Create or Extend in this session.",
          node.name().c_str()));
    }
  }


  // 3. Merge the versions field.
  // 3. 合并版本字段。
  int old_node_size = gdef.node_size();
  gdef.mutable_node()->MergeFrom(extension_def.node());
  TF_RETURN_IF_ERROR(
      AddDefaultAttrsToGraphDef(&gdef, *flib_def_.get(), old_node_size));
  // Merge versions
  if (gdef.has_versions()) {
    if (gdef.versions().producer() != extension_def.versions().producer()) {
      return errors::InvalidArgument(
          "Can't extend GraphDef at version ", gdef.versions().producer(),
          " with graph at version ", extension_def.versions().producer());
    }
    VersionDef* versions = gdef.mutable_versions();
    versions->set_min_consumer(std::max(
        versions->min_consumer(), extension_def.versions().min_consumer()));
    if (extension_def.versions().bad_consumers_size()) {
      // Add new bad_consumers that aren't already marked bad.
      // 添加尚未标记为坏的新的 bad_consumers。
	  
      // Note: This implementation is quadratic time if there are many calls to
      // ExtendLocked with many bad consumers.  Since this is unlikely, and
      // fixing it would require data structures outside of this routine,
      // quadratic time it is.
	  // 注意:如果对 ExtendLocked 有很多不好的消费者的呼叫很多,这个实现是二次的。
	  // 由于这不太可能,并且修复它将需要这个例程之外的数据结构,它是二次的。
      auto* bad_consumers = versions->mutable_bad_consumers();
      const std::unordered_set<int> existing(bad_consumers->begin(),
                                             bad_consumers->end());
      for (const int v : extension_def.versions().bad_consumers()) {
        if (existing.find(v) == existing.end()) {
          bad_consumers->Add(v);
        }
      }
    }


  } else {
    gdef.mutable_versions()->CopyFrom(extension_def.versions());
  }


  // 4. Copy the function library from this execution state.
  // 4. 从这个执行状态复制函数库。
  // NOTE(mrry): To match the previous behavior, the first GraphDef
  // passed to a session will contain the function library that is
  // used for all subsequent execution states.
  // 为了匹配以前的行为,传递给会话的第一个 GraphDef 将包含用于所有后续执行状态的函数库。
  *gdef.mutable_library() = flib_def_->ToProto();


  // 5. Validate that the final graphdef is valid.
  // 5. 验证最终的graphdef是否有效。
  if (gdef.versions().producer() >= 5) {
    // Validate the graph: we assume that merging two valid graphs
    // should maintain graph validity.
	// 验证图:我们假设合并两个有效的图形应该保持图形的有效性。
    TF_RETURN_IF_ERROR(graph::ValidateGraphDef(gdef, *flib_def_.get()));
  }


  // 6. Add the extension.
  SimpleGraphExecutionStateOptions combined_options;
  combined_options.device_set = device_set_;
  combined_options.session_options = session_options_;
  combined_options.stateful_placements = stateful_placements_;


  // NOTE(mrry): `gdef` is no longer valid after the constructor executes.
  // 在构造函数执行后,`gdef` 不再有效。
  std::unique_ptr<SimpleGraphExecutionState> new_execution_state(
      new SimpleGraphExecutionState(&gdef, combined_options));


  TF_RETURN_IF_ERROR(AddDefaultAttrsToGraphDef(
      &new_execution_state->original_graph_def_, *flib_def_.get(), 0));
  if (!session_options_->config.graph_options().place_pruned_graph()) {
    // TODO(mrry): Refactor InitBaseGraph() so that we don't have to
    // pass an empty BuildGraphOptions (that isn't going to be used
    // when place_pruned_graph is false).
	// 重构 InitBaseGraph() 使我们不必传递一个空的 BuildGraphOptions(当 place_pruned_graph 为 false 时不会被使用)。
    TF_RETURN_IF_ERROR(new_execution_state->InitBaseGraph(BuildGraphOptions()));
  }
  *out = std::move(new_execution_state);


  // TODO(mrry): This is likely to be used for non-throughput-sensitive
  // interactive workloads, but in future we may want to transfer other
  // parts of the placement and/or cost model.
  // 这很可能用于非吞吐量敏感的交互式工作负载,但将来我们可能希望转移其他部分的展示位置 and/or 成本模型。
  return Status::OK();
}


void SimpleGraphExecutionState::SaveStatefulNodes(Graph* graph) {
  for (Node* n : graph->nodes()) {
    if (n->op_def().is_stateful()) {
      VLOG(2) << "Saving " << n->DebugString();
      stateful_placements_[n->name()] = n->assigned_device_name();
    }
  }
}


void SimpleGraphExecutionState::RestoreStatefulNodes(Graph* graph) {
  for (Node* n : graph->nodes()) {
    if (n->op_def().is_stateful()) {
      auto iter = stateful_placements_.find(n->name());
      if (iter != stateful_placements_.end()) {
        n->set_assigned_device_name(iter->second);
        VLOG(2) << "Restored " << n->DebugString();
      }
    }
  }
}


Status SimpleGraphExecutionState::InitBaseGraph(
    const BuildGraphOptions& options) {
  const GraphDef* graph_def = &original_graph_def_;


#ifndef IS_MOBILE_PLATFORM
  GraphDef optimized_graph;


  const RewriterConfig& rewrite_options =
      session_options_->config.graph_options().rewrite_options();


  if (grappler::MetaOptimizerEnabled(rewrite_options)) {
    // Adding this functionalty in steps. The first step is to make sure
    // we don't break dependencies. The second step will be to turn the
    // functionality on by default.
	// 按步骤添加此功能。 第一步是确保我们不会破坏依赖关系。 第二步是默认打开功能。
    grappler::GrapplerItem item;
    item.id = "tf_graph";
    item.graph = original_graph_def_;


    item.fetch = options.fetch_endpoints;
    item.fetch.insert(item.fetch.end(), options.target_nodes.begin(),
                      options.target_nodes.end());


    Status s;
    if (!options.feed_endpoints.empty()) {
      std::unordered_set<string> feeds(options.feed_endpoints.begin(),
                                       options.feed_endpoints.end());
      for (const NodeDef& node : original_graph_def_.node()) {
        if (feeds.find(node.name()) == feeds.end()) {
          continue;
        }
        if (node.attr().count("dtype") == 0 ||
            node.attr().count("shape") == 0) {
          s = errors::InvalidArgument("Missing node shape or type");
          break;
        }
        TensorShape shape(node.attr().at("shape").shape());
        DataType type = node.attr().at("dtype").type();
        Tensor fake_input(type, shape);
        item.feed.emplace_back(node.name(), fake_input);
      }
    }


    if (s.ok()) {
      std::unordered_map<string, DeviceProperties> device_map;
      for (const auto& device : device_set_->devices()) {
        device_map[device->name()] =
            grappler::GetDeviceInfo(device->parsed_name());
      }
      grappler::VirtualCluster cluster(device_map);
      s = grappler::RunMetaOptimizer(item, rewrite_options, &cluster,
                                     &optimized_graph);
    }
    if (s.ok()) {
      graph_def = &optimized_graph;
    }
  }
#endif  // IS_MOBILE_PLATFORM


  std::unique_ptr<Graph> new_graph(new Graph(OpRegistry::Global()));
  GraphConstructorOptions opts;
  TF_RETURN_IF_ERROR(ConvertGraphDefToGraph(opts, *graph_def, new_graph.get()));
  for (const Node* n : new_graph->nodes()) {
    VLOG(2) << "Mapping " << n->name() << " to " << n->cost_id();
    node_name_to_cost_id_map_[n->name()] = n->cost_id();
  }
  if (session_options_ &&
      session_options_->config.graph_options().place_pruned_graph()) {
    // Rewrite the graph before placement. 在放置前重写图形。
    rewrite_metadata_.reset(new subgraph::RewriteGraphMetadata);
    TF_RETURN_IF_ERROR(subgraph::RewriteGraphForExecution(
        new_graph.get(), options.feed_endpoints, options.fetch_endpoints,
        options.target_nodes, device_set_->client_device()->attributes(),
        options.use_function_convention, rewrite_metadata_.get()));
  }
 
  // Save stateful placements before placing. 保存有状态的位置。
  RestoreStatefulNodes(new_graph.get());


  CostModel costs(true /*is_global*/);
  {
    mutex_lock l(mu_);
    costs_.InitFromGraph(*new_graph.get());
    costs.MergeFromGlobal(costs_);
  }


  GraphOptimizationPassOptions optimization_options;
  optimization_options.session_options = session_options_;
  optimization_options.graph = &new_graph;
  optimization_options.flib_def = flib_def_.get();
  optimization_options.device_set = device_set_;
  optimization_options.cost_model = &costs;


  TF_RETURN_IF_ERROR(OptimizationPassRegistry::Global()->RunGrouping(
      OptimizationPassRegistry::PRE_PLACEMENT, optimization_options));


  SimplePlacer placer(new_graph.get(), device_set_, session_options_);
  // TODO(mrry): Consider making the SimplePlacer cancelable.
  TF_RETURN_IF_ERROR(placer.Run());


  TF_RETURN_IF_ERROR(OptimizationPassRegistry::Global()->RunGrouping(
      OptimizationPassRegistry::POST_PLACEMENT, optimization_options));


  SaveStatefulNodes(new_graph.get());
  graph_ = new_graph.release();
  return Status::OK();
}


Status SimpleGraphExecutionState::BuildGraph(
    const BuildGraphOptions& options, std::unique_ptr<SimpleClientGraph>* out) {
  VLOG(1) << "BuildGraph";
  if (!graph_) {
    // It is only valid to call this method directly when the original graph
    // was created with the option `place_pruned_graph == false`.
	// 当使用`place_pruned_graph == false`选项创建原始图形时,直接调用此方法才有效。
    return errors::Internal(
        "Attempted to prune a graph that has not been fully initialized.");
  }
  std::unique_ptr<Graph> ng(new Graph(flib_def_.get()));
  CopyGraph(*graph_, ng.get());


  subgraph::RewriteGraphMetadata rewrite_metadata;
  if (session_options_ == nullptr ||
      !session_options_->config.graph_options().place_pruned_graph()) {
    // Extract the subset of the graph that needs to be run, adding feed/fetch ops as needed.
    // 提取需要运行的图表子集,根据需要添加 feed/fetch 操作。
    TF_RETURN_IF_ERROR(subgraph::RewriteGraphForExecution(
        ng.get(), options.feed_endpoints, options.fetch_endpoints,
        options.target_nodes, device_set_->client_device()->attributes(),
        options.use_function_convention, &rewrite_metadata));
  } else {
    // This SimpleGraphExecutionState represents a graph that was pruned when this was constructed,
    // so we copy the metadata from a member variable.
    // 这个 SimpleGraphExecutionState 表示一个在构造时被修剪的图形,所以我们从成员变量复制元数据。
    CHECK(rewrite_metadata_);
    rewrite_metadata = *rewrite_metadata_;
  }


  CHECK_EQ(options.feed_endpoints.size(), rewrite_metadata.feed_types.size());
  CHECK_EQ(options.fetch_endpoints.size(), rewrite_metadata.fetch_types.size());


  // Make a fresh copy of the function library for the client graph.
  // 为客户端图创建功能库的新副本。
  std::unique_ptr<FunctionLibraryDefinition> flib(
      new FunctionLibraryDefinition(*flib_def_));


  // TODO(andydavis): Clarify optimization pass requirements around CostModel.
  CostModel costs(true /*is_global*/);
  costs.MergeFromGlobal(costs_);
  GraphOptimizationPassOptions optimization_options;
  optimization_options.session_options = session_options_;
  optimization_options.graph = &ng;
  optimization_options.flib_def = flib.get();
  optimization_options.device_set = device_set_;
  optimization_options.cost_model = &costs;


  TF_RETURN_IF_ERROR(OptimizationPassRegistry::Global()->RunGrouping(
      OptimizationPassRegistry::POST_REWRITE_FOR_EXEC, optimization_options));


  // Copy the extracted graph in order to make its node ids dense,
  // since the local CostModel used to record its stats is sized by
  // the largest node id.
  // 复制提取的图形以使其节点密集,因为用于记录其统计信息的本地 CostModel 的大小由最大的节点 ID 定义。
  std::unique_ptr<SimpleClientGraph> dense_copy(
      new SimpleClientGraph(std::move(flib), rewrite_metadata.feed_types,
                            rewrite_metadata.fetch_types));
  CopyGraph(*ng, &dense_copy->graph);


  // TODO(vrv): We should check invariants of the graph here.


  *out = std::move(dense_copy);
  return Status::OK();
}


}  // namespace tensorflow


import pandas as pd import networkx as nx import numpy as np import itertools # --- Constants and Assumptions --- # These should be clearly stated and can be modified. VOLTAGE_KV = 10.0 # Line voltage in kV ROOT_3 = np.sqrt(3) BASE_DG_CAPACITY_KW = 300.0 # Initial capacity for each DG N_DG = 8 # Failure rates from problem description FAILURE_RATE_DG_PERCENT = 0.5 / 100.0 # FAILURE_RATE_USER_PERCENT = 0.5 / 100.0 # Not directly used in this simplified line-fault model for widespread outages # FAILURE_RATE_SWITCH_PERCENT = 0.2 / 100.0 # Assuming switch failures manifest as line failures or inability to operate tie FAILURE_RATE_LINE_PER_KM = 0.002 # Per km per year (assuming rates are annual) # Costs (placeholders - these are critical for actual risk values) # Value of Lost Load (VoLL) in monetary units per kW per hour. # For risk = P * C, if P is annual probability, C should be impact of one event. # Let's define C_loss as total kW unserved * a severity factor. # Or, if we want an annual risk cost: P_annual_fault * kW_unserved * hours_outage * cost_per_kWh # For simplicity, using $/kW of unserved load for the consequence C. COST_VOLL_PER_KW = 10.0 # Example: $10 per kW of unserved load AVG_OUTAGE_DURATION_H = 4 # Example: average hours for an outage, if converting to energy # Cost of Overload (Consequence C_over) # This can be complex: accelerated aging, tripping, damage. # Simplified: A penalty if any line is overloaded in a given state. COST_PENALTY_FOR_ANY_OVERLOAD = 1000.0 # Example: $1000 penalty if system is in an overloaded state # Or, a cost per MWh of overloaded energy, or per overloaded line. # Line and Feeder Capacities # Main feeder rated current from problem: 220A. # P_rated_feeder_kW = ROOT_3 * VOLTAGE_KV * FEEDER_RATED_CURRENT_A * 1.0 (pf=1) # = 1.732 * 10 * 220 = 3810.4 kW (approx 3.8 MW, problem says 2.2MW for 220A, implies lower pf or different basis) # Let's use current as the primary limit. FEEDER_RATED_CURRENT_A = 220.0 # Assumption for individual line segments: For this model, we'll assume all lines # have a rated current equal to the main feeder. This is a strong simplification. # A more detailed model would assign ratings based on conductor types or downstream load. LINE_RATED_CURRENT_A = 100.0 # More conservative assumption for individual segments than 220A. Needs proper engineering values. # For lines directly from substation, perhaps 220A is more appropriate. # Let's use a dictionary for specific line ratings if known, else default. DEFAULT_LINE_RATED_CURRENT_A = 100.0 # Tie Line Capacity TIE_LINE_RATED_CURRENT_A = 150.0 # Assumption, should be based on tie switch/line capacity # DG Locations (Node IDs from 1 to 62) - Based on Figure 1 interpretation DG_LOCATIONS_KW = { 6: BASE_DG_CAPACITY_KW, 10: BASE_DG_CAPACITY_KW, 15: BASE_DG_CAPACITY_KW, 27: BASE_DG_CAPACITY_KW, 31: BASE_DG_CAPACITY_KW, 37: BASE_DG_CAPACITY_KW, 50: BASE_DG_CAPACITY_KW, 58: BASE_DG_CAPACITY_KW } # Tie Switches: (node1, node2, switch_id_text) - normally open # Interpretation based on careful review of Figure 1: # S13-1: (13, 22) - Intra-Feeder 1 (Connects two branches of Feeder 1) # S29-2: (29, 42) - Intra-Feeder 2 (Connects two branches of Feeder 2) # S62-3: (62, 19) - Inter-Feeder (Connects Feeder 3 (node 62) to Feeder 1 (node 19)) TIE_SWITCHES_INFO = [ {'nodes': (13, 22), 'id': 'S13-1', 'type': 'intra-F1', 'capacity_A': TIE_LINE_RATED_CURRENT_A}, {'nodes': (29, 42), 'id': 'S29-2', 'type': 'intra-F2', 'capacity_A': TIE_LINE_RATED_CURRENT_A}, {'nodes': (62, 19), 'id': 'S62-3', 'type': 'inter-F3_F1', 'capacity_A': TIE_LINE_RATED_CURRENT_A} ] # This interpretation means Feeder 2 cannot directly receive support from F1 or F3. # If problem implies all feeders can support each other, TIE_SWITCHES_INFO would need redefinition. # Substation connection points (source nodes for feeders) # CB1 -> Node 1, CB2 -> Node 23, CB3 -> Node 43 # Node 0 will represent the main grid / infinite source. SOURCE_NODE = 0 SUBSTATION_CONNECTIONS = { 'CB1': (SOURCE_NODE, 1), 'CB2': (SOURCE_NODE, 23), 'CB3': (SOURCE_NODE, 43) } # Capacity of connection from source to substation nodes (effectively feeder capacity) SUBSTATION_LINE_CAPACITY_A = FEEDER_RATED_CURRENT_A # --- Data Loading Functions --- def load_load_data(filename="C题附件:有源配电网62节点系统基本参数.xlsx - 表1 有源配电网62节点系统负荷参数.csv"): df = pd.read_csv(filename) df.columns = ['node_id', 'load_kw'] # Convert node_id to int if it's not already df['node_id'] = df['node_id'].astype(int) return df.set_index('node_id')['load_kw'].to_dict() def load_topology_data(filename="C题附件:有源配电网62节点系统基本参数.xlsx - 表2 有源配电网62节点系统拓扑参数.csv"): df = pd.read_csv(filename) # Rename columns for easier access (assuming standard Chinese headers) df.columns = ['line_num', 'from_node', 'to_node', 'length_km', 'resistance_ohm', 'reactance_ohm'] # Convert relevant columns to numeric for col in ['from_node', 'to_node', 'length_km', 'resistance_ohm', 'reactance_ohm']: df[col] = pd.to_numeric(df[col], errors='coerce') return df # --- Core Power Grid Model Class --- class PowerGridModel: def __init__(self, load_data, topology_data, dg_locations_kw, tie_switches_info, substation_connections): self.loads_kw = load_data self.topology_df = topology_data self.dg_kw = dg_locations_kw.copy() # Allow modification for different scenarios self.tie_switches_info = tie_switches_info self.substation_connections = substation_connections self.graph = self._build_graph() self.feeder_info = self._identify_feeders() def _build_graph(self): G = nx.Graph() # Use Graph for undirected, or DiGraph if flow direction is fixed by sources # Add nodes with load and DG info all_nodes = set(self.topology_df['from_node']) | set(self.topology_df['to_node']) for node_id in all_nodes: node_id = int(node_id) # Ensure int G.add_node(node_id, load_kw=self.loads_kw.get(node_id, 0), dg_kw=self.dg_kw.get(node_id, 0)) # Add lines from topology data for _, row in self.topology_df.iterrows(): u, v = int(row['from_node']), int(row['to_node']) G.add_edge(u, v, id=row['line_num'], length_km=row['length_km'], resistance_ohm=row['resistance_ohm'], # reactance_ohm=row['reactance_ohm'], # Ignoring reactance as per problem rated_current_a=DEFAULT_LINE_RATED_CURRENT_A, # Default, can be refined failed=False) # Add substation connections (virtual lines from a common source) # These represent the main feeder lines from CBs G.add_node(SOURCE_NODE, type='source') for cb_id, (src, dest_node) in self.substation_connections.items(): G.add_edge(src, dest_node, id=cb_id, length_km=0.01, resistance_ohm=0.001, # Minimal impedance rated_current_a=SUBSTATION_LINE_CAPACITY_A, type='substation_link', failed=False) return G def _get_subgraph_with_operational_lines(self, graph_to_copy, faulty_line_edge=None): """Creates a subgraph considering only non-failed lines and open tie switches.""" g_op = graph_to_copy.copy() # Remove failed lines lines_to_remove = [] if faulty_line_edge: # faulty_line_edge is (u,v) if g_op.has_edge(*faulty_line_edge): lines_to_remove.append(faulty_line_edge) for u, v, data in list(g_op.edges(data=True)): if data.get('failed', False): lines_to_remove.append((u,v)) g_op.remove_edges_from(lines_to_remove) # Normally, tie switches are open. For restoration, specific ones might be closed. # This base function assumes they are open unless explicitly handled by restoration logic. return g_op def _identify_feeders(self): """Identifies nodes belonging to each feeder under normal operation (tie switches open).""" g_normal = self._get_subgraph_with_operational_lines(self.graph) feeder_info = {} # {'CB1': {nodes}, 'CB2': {nodes}, ...} for cb_id, (src_node, start_node) in self.substation_connections.items(): if g_normal.has_node(start_node) and g_normal.has_node(src_node) and nx.has_path(g_normal, src_node, start_node): # Find all nodes reachable from start_node without passing through another substation's start_node # or the main source node again, after removing other substation links. temp_g = g_normal.copy() other_cb_links = [] for other_cb, (s,d) in self.substation_connections.items(): if other_cb != cb_id and temp_g.has_edge(s,d): other_cb_links.append((s,d)) temp_g.remove_edges_from(other_cb_links) if nx.has_path(temp_g, src_node, start_node): # All nodes in the component connected to start_node, excluding the source itself component_nodes = nx.node_connected_component(temp_g.subgraph( [n for n in temp_g.nodes if n != src_node or n == start_node] # Consider start_node part of feeder ), start_node) feeder_info[cb_id] = component_nodes else: # Should not happen if graph is built correctly feeder_info[cb_id] = {start_node} if start_node in g_normal else set() else: feeder_info[cb_id] = {start_node} if start_node in g_normal else set() return feeder_info def _calculate_line_current_kw(self, power_kw): """Calculates current (A) given power (kW) at VOLTAGE_KV (line-to-line).""" if VOLTAGE_KV <= 0: return float('inf') return abs(power_kw) / (ROOT_3 * VOLTAGE_KV * 1.0) # Assumed PF=1 for current calculation from P def _get_downstream_info(self, G, line_u, line_v, source_nodes_for_feeder): """ Calculates total load and DG power downstream of a directed line (u,v), assuming v is further from the source_node for this path. G: graph to operate on (can be a faulted graph) source_nodes_for_feeder: list of possible source nodes for the current connected component. """ # Temporarily make graph directed from source to loads to find downstream nodes # This is tricky if the graph is not purely radial or has loops after closing ties. # A simpler approach for radial sections: # Check connectivity from sources to line_v, if line_u is removed. # If line_v is disconnected from all sources when (u,v) is cut, then everything # in the component of line_v is downstream. # Create a copy of G to modify temp_g = G.copy() if not temp_g.has_edge(line_u, line_v): return [], 0, 0 # Line doesn't exist temp_g.remove_edge(line_u, line_v) downstream_nodes = set() # Check which side (u or v) is disconnected from the source(s) # Assume u is closer to source, v is further. # If v is still connected to a source, then (u,v) might be part of a loop or fed from elsewhere. # A robust way: find path from source to v. If (u,v) is on all paths, then v is downstream of u via this line. # For radial feeders (normal operation): # If we consider (u,v) where u is parent of v: component_of_v = set() q = [line_v] visited = {line_u, line_v} # Start by marking u as visited (as if coming from u) # If line_v is connected to any source_node without passing through line_u v_connected_to_source_alt_path = False for src in source_nodes_for_feeder: if nx.has_path(temp_g, src, line_v): v_connected_to_source_alt_path = True break if v_connected_to_source_alt_path: # (u,v) is part of a loop or v is fed from elsewhere # This simple downstream logic is insufficient for meshed networks. # For now, assume radial for this part of flow calculation. # A more complex flow calculation (Newton-Raphson) would be needed for meshed. # Given problem constraints, assume feeders are normally radial. pass # This line might not have a clear "downstream" if looped. # If v is disconnected from source when (u,v) is cut, then its component is downstream. # Check connectivity for v in temp_g (where (u,v) is removed) v_still_connected = any(nx.has_path(temp_g, src, line_v) for src in source_nodes_for_feeder if src in temp_g) if not v_still_connected: # v is now isolated from source, so its component is downstream of (u,v) component_of_v = nx.node_connected_component(temp_g, line_v) else: # v is still connected, means (u,v) might be redundant or complex. # Try to determine direction based on distance from source dist_u = float('inf') dist_v = float('inf') for src in source_nodes_for_feeder: if src not in G: continue if nx.has_path(G, src, line_u): dist_u = min(dist_u, nx.shortest_path_length(G, src, line_u)) if nx.has_path(G, src, line_v): dist_v = min(dist_v, nx.shortest_path_length(G, src, line_v)) if dist_v > dist_u : # v is downstream of u # Find component of v if (u,v) is removed and v is not connected to source g_temp_removed_edge = G.copy() g_temp_removed_edge.remove_edge(line_u,line_v) is_v_conn_to_src = False for src_node_feeder in source_nodes_for_feeder: if src_node_feeder in g_temp_removed_edge and nx.has_path(g_temp_removed_edge, src_node_feeder, line_v): is_v_conn_to_src = True break if not is_v_conn_to_src: component_of_v = nx.node_connected_component(g_temp_removed_edge, line_v) else: # v is still connected, (u,v) is likely a loop closing line. Flow is complex. # For simplicity, this function will return 0 flow for loop lines if direction is ambiguous. return [], 0, 0 elif dist_u > dist_v: # u is downstream of v (swap them) # similar logic for u g_temp_removed_edge = G.copy() g_temp_removed_edge.remove_edge(line_u,line_v) is_u_conn_to_src = False for src_node_feeder in source_nodes_for_feeder: if src_node_feeder in g_temp_removed_edge and nx.has_path(g_temp_removed_edge, src_node_feeder, line_u): is_u_conn_to_src = True break if not is_u_conn_to_src: component_of_v = nx.node_connected_component(g_temp_removed_edge, line_u) # component_of_v is actually comp of u else: return [], 0, 0 else: # Equidistant or complex, cannot determine simple downstream for this line return [], 0, 0 downstream_nodes = component_of_v total_downstream_load_kw = sum(G.nodes[n]['load_kw'] for n in downstream_nodes) total_downstream_dg_kw = sum(G.nodes[n]['dg_kw'] for n in downstream_nodes if G.nodes[n]['dg_kw'] > 0) return list(downstream_nodes), total_downstream_load_kw, total_downstream_dg_kw def calculate_power_flows_and_currents(self, current_graph_state, active_dgs_kw): """ Simplified power flow for radial networks or parts of networks. Returns dict of line flows and currents, and substation powers. Flows: {(u,v): power_kw} where power_kw > 0 means u to v. Currents: {(u,v): current_A} Substation_powers: {'CB1': power_kw_drawn_from_substation} """ line_flows_kw = {} line_currents_a = {} substation_powers_kw = {} # Update DG outputs in the graph state for node_id, dg_val in active_dgs_kw.items(): if node_id in current_graph_state: current_graph_state.nodes[node_id]['dg_kw'] = dg_val for node_id in current_graph_state.nodes(): # Reset others if not in active_dgs_kw if node_id not in active_dgs_kw and 'dg_kw' in current_graph_state.nodes[node_id]: if current_graph_state.nodes[node_id].get('type') != 'source': # Don't zero out if it was never a DG current_graph_state.nodes[node_id]['dg_kw'] = 0 # Determine connected components and their sources # This is a very simplified load flow. It assumes power flows from sources (substations) # down to loads. DG power reduces the load seen by upstream sections. # It does not handle loops well without iterative methods (e.g. Hardy Cross or Newton-Raphson). # For each feeder, calculate flows assuming radial structure # This is an approximation. A full AC or DC power flow is more accurate. # Initialize all line flows to 0 for u, v in current_graph_state.edges(): line_flows_kw[(u,v)] = 0 line_flows_kw[(v,u)] = 0 # For bi-directional calculation needs line_currents_a[(u,v)] = 0 # Iterate multiple times for flow distribution in case of ties or complex paths # This is a placeholder for a proper iterative flow solution. # For now, a topological sort based flow for radial parts. processed_nodes_for_flow_calc = set() for cb_id, (src_node, start_node) in self.substation_connections.items(): if not current_graph_state.has_node(start_node) or not nx.is_connected(current_graph_state.subgraph([n for n in current_graph_state.nodes() if n != SOURCE_NODE])): # This feeder might be entirely down if start_node is disconnected from actual nodes substation_powers_kw[cb_id] = 0 continue # Get nodes for this feeder (dynamic based on current_graph_state) # Nodes connected to start_node, excluding SOURCE_NODE, if start_node is connected to SOURCE_NODE feeder_nodes_component = set() if current_graph_state.has_edge(src_node, start_node): temp_g_for_feeder = current_graph_state.copy() # Remove other substation links to isolate this feeder's component other_links_to_remove = [] for other_cb, (s,d) in self.substation_connections.items(): if other_cb != cb_id and temp_g_for_feeder.has_edge(s,d): other_links_to_remove.append((s,d)) temp_g_for_feeder.remove_edges_from(other_links_to_remove) if nx.has_path(temp_g_for_feeder, src_node, start_node): try: # Consider only the part of the graph reachable from start_node, not crossing back to SOURCE_NODE # except via the designated start_node path. search_nodes = [n for n in temp_g_for_feeder.nodes if n != src_node] sub_graph_feeder = temp_g_for_feeder.subgraph(search_nodes) if start_node in sub_graph_feeder: feeder_nodes_component = nx.node_connected_component(sub_graph_feeder, start_node) except nx.NetworkXError: # if start_node not in subgraph feeder_nodes_component = set() if not feeder_nodes_component: substation_powers_kw[cb_id] = 0 continue # Order nodes from furthest to closest to substation for power accumulation # This is for radial feeders. If loops exist, this is not sufficient. # Using BFS layers from start_node # Net load at each node (Load - DG) node_net_power_kw = {} for node in feeder_nodes_component: node_net_power_kw[node] = current_graph_state.nodes[node]['load_kw'] - current_graph_state.nodes[node]['dg_kw'] # Accumulate power up towards the substation # This requires a tree traversal (e.g., DFS post-order traversal) from leaves to root. # For simplicity, if the feeder is a tree rooted at start_node: if nx.is_tree(current_graph_state.subgraph(feeder_nodes_component | {start_node})): # Check if it's a tree # Create a directed tree towards the source for easier traversal # This part is complex if graph is not a tree. # For now, sum all net loads on the feeder as the substation power (approximation) total_feeder_net_load = sum(node_net_power_kw[n] for n in feeder_nodes_component) substation_powers_kw[cb_id] = total_feeder_net_load # Distribute this flow down the lines (highly simplified) # A proper method: for each line, sum net_power of all nodes in subtree rooted by that line. # This simplified flow calculation is a major placeholder. # For overload risk, we need per-line flows. # Simplified: assume current_graph_state is a tree rooted at start_node for this feeder # Use BFS to assign flow from start_node downwards # This is not a full power flow, but an estimation for line loading. # Build a directed graph for this feeder based on BFS from start_node # This is just for flow assignment direction. # Actual flow needs to sum up demands from downstream. # For each edge (u,v) in the feeder: # Determine parent (closer to start_node) and child # Power on (parent, child) = sum of net loads in subtree rooted at child. # This is complex to implement robustly here without a full flow algorithm. # Fallback: Use the _get_downstream_info logic if possible, iterate edges # This needs to be called carefully to avoid double counting or misdirection in non-radial. # For now, this function will primarily return substation_powers_kw and # leave detailed line_flows_kw and line_currents_a for a more robust implementation # or accept its high level of approximation. # Let's try a slightly better approximation for line flows on a tree: # For each edge (u,v) in the feeder tree (rooted at start_node) # Assume u is parent of v. Power(u,v) = sum of net_power for all nodes in subtree of v. if start_node in feeder_nodes_component: # Should be try: dfs_edges = list(nx.dfs_edges(nx.bfs_tree(current_graph_state.subgraph(feeder_nodes_component), start_node), source=start_node)) # Calculate power for each node including its children's power node_total_subtree_power = node_net_power_kw.copy() for u, v in reversed(dfs_edges): # From leaves up to root if u in node_total_subtree_power and v in node_total_subtree_power: node_total_subtree_power[u] += node_total_subtree_power[v] for u,v in dfs_edges: # From root down if v in node_total_subtree_power: flow = node_total_subtree_power[v] line_flows_kw[(u,v)] = flow line_flows_kw[(v,u)] = -flow # Convention for direction line_currents_a[(u,v)] = self._calculate_line_current_kw(flow) except Exception as e: # print(f"Warning: Could not perform tree-based flow for {cb_id} due to {e}") pass # Keep substation power as sum, line flows might be inaccurate else: # Not a tree, flow calculation is more complex. # print(f"Warning: Feeder {cb_id} is not a tree. Simplified flow may be inaccurate.") total_feeder_net_load = sum(node_net_power_kw[n] for n in feeder_nodes_component if n in node_net_power_kw) substation_powers_kw[cb_id] = total_feeder_net_load # Line flows in meshed networks require iterative solvers. # For now, this part will be very approximate for meshed sections. # Handle reverse power flow and inter-feeder DG adjustment # "分布式能源不得向上级电网倒送功率" # "可以在相邻馈线间进行调节" for cb_id in list(substation_powers_kw.keys()): if substation_powers_kw[cb_id] < 0: # Reverse power flow excess_dg_on_feeder = -substation_powers_kw[cb_id] # Try to transfer to other feeders via inter-feeder tie lines # This logic is complex and needs careful state management. # For Q1, a simpler approach might be DG curtailment on that feeder. # Find inter-feeder tie switches connected to this feeder # Example: S62-3 connects Feeder 3 (node 62) to Feeder 1 (node 19) # If Feeder 1 has excess_dg, it might try to send to Feeder 3 via (19,62) # This is an advanced feature. For now, assume DG curtailment if倒送. # To implement curtailment: identify DGs on this feeder, reduce their output # proportionally until substation_powers_kw[cb_id] >= 0. # This would require re-calculating flows. # For now, just flag it. # print(f"Warning: Reverse power flow on {cb_id} of {substation_powers_kw[cb_id]} kW. DG curtailment or transfer needed.") # A simple curtailment: dG_on_feeder_nodes = [n for n in self.feeder_info.get(cb_id, set()) if n in active_dgs_kw and active_dgs_kw[n] > 0] total_dg_cap_on_feeder = sum(active_dgs_kw[n] for n in dG_on_feeder_nodes) if total_dg_cap_on_feeder > 0: curtail_ratio = min(1.0, excess_dg_on_feeder / total_dg_cap_on_feeder) if total_dg_cap_on_feeder >0 else 0 for dg_node in dG_on_feeder_nodes: active_dgs_kw[dg_node] *= (1 - curtail_ratio) # Flows need to be recalculated after curtailment. This suggests an iterative solution. # For this submission, we'll assume this check is done *before* final flow calc, # or simply note the violation. # To avoid recursion here, this function should ideally take DGs as fixed input. # The adjustment logic should be outside or iterative. pass return line_flows_kw, line_currents_a, substation_powers_kw def calculate_overload_risk(self): """ Calculates overload risk for the current DG setup. Assumes DGs are at their BASE_DG_CAPACITY_KW. """ # Get current operational graph (no faults, ties normally open) g_op = self._get_subgraph_with_operational_lines(self.graph) # Calculate power flows and currents # Need to handle DG outputs properly. current_dg_outputs = self.dg_kw.copy() # Use the model's current DG settings # Iterative step for DG curtailment if reverse power flow: # This is a simplified loop. A more robust solution uses optimization or better heuristics. for _iter in range(3): # Max 3 iterations for adjustment line_flows, line_currents, substation_powers = self.calculate_power_flows_and_currents(g_op, current_dg_outputs) reverse_power_detected = False for cb_id, power_kw in substation_powers.items(): if power_kw < -1e-3: # Small threshold for倒送 reverse_power_detected = True # print(f"Info: Reverse power on {cb_id} ({power_kw:.2f} kW). Attempting curtailment.") excess_dg_on_feeder = abs(power_kw) feeder_nodes_for_cb = self.feeder_info.get(cb_id, set()) dG_on_feeder_nodes = [n for n in feeder_nodes_for_cb if n in current_dg_outputs and current_dg_outputs[n] > 0] total_dg_cap_on_feeder = sum(current_dg_outputs[n] for n in dG_on_feeder_nodes) if total_dg_cap_on_feeder > 1e-3 : # Avoid division by zero curtail_amount_total = excess_dg_on_feeder for dg_node in dG_on_feeder_nodes: # Proportional curtailment proportion = current_dg_outputs[dg_node] / total_dg_cap_on_feeder curtail_this_dg = proportion * curtail_amount_total current_dg_outputs[dg_node] = max(0, current_dg_outputs[dg_node] - curtail_this_dg) else: # No DG to curtail, reverse power might be from other sources or model issue pass if not reverse_power_detected: break # Final flows after potential curtailment line_flows, line_currents, substation_powers = self.calculate_power_flows_and_currents(g_op, current_dg_outputs) overloaded_lines = [] for u, v, data in g_op.edges(data=True): if data.get('type') == 'substation_link': continue # Don't check substation links themselves for overload here current = line_currents.get((u,v), 0) # If flow is from v to u, current might be stored as current_uv = -current_vu # Take absolute value of flow for current calculation, or ensure current is always positive. # The _calculate_line_current_kw uses abs(power_kw) so current should be positive. rated_current = data.get('rated_current_a', DEFAULT_LINE_RATED_CURRENT_A) if current > 1.1 * rated_current: overloaded_lines.append({'edge': (u,v), 'current': current, 'rated': rated_current, 'over_by_%': (current/(1.1*rated_current)-1)*100 if rated_current>0 else float('inf')}) if overloaded_lines: # print(f"System Overload Detected. Overloaded lines: {overloaded_lines}") # P_over = 1 (for this deterministic scenario) # C_over = fixed penalty or sum of penalties risk_overload = 1.0 * COST_PENALTY_FOR_ANY_OVERLOAD # Or, sum of consequences for each overloaded line, if C_over is per line. # risk_overload = sum(some_cost_function(ol['over_by_%']) for ol in overloaded_lines) else: # print("System is NOT overloaded in the base case.") risk_overload = 0.0 return risk_overload, overloaded_lines, substation_powers, current_dg_outputs def calculate_load_loss_risk(self): """ Calculates total load loss risk by considering single line faults. R_loss = sum(P_fault_i * C_loss_i) P_fault_i = annual probability of fault i C_loss_i = consequence of fault i (e.g., unserved_load_kw * COST_VOLL_PER_KW) """ total_load_loss_risk = 0.0 detailed_fault_impacts = [] # Iterate through all operational lines (excluding substation virtual links for fault simulation) original_edges = [ (u,v,data) for u,v,data in self.graph.edges(data=True) if data.get('type') != 'substation_link' and not data.get('is_tie', False)] for u_fault, v_fault, line_data_faulted in original_edges: faulty_edge = (u_fault, v_fault) line_length_km = line_data_faulted.get('length_km', 0) # Probability of this specific line failing (annual) # Assuming failure rates are independent and this is the probability of this line being the one to fail. prob_line_fault = line_length_km * FAILURE_RATE_LINE_PER_KM if prob_line_fault == 0: continue # --- Simulate fault --- g_faulted = self.graph.copy() if not g_faulted.has_edge(*faulty_edge): continue g_faulted.edges[faulty_edge]['failed'] = True # Mark as failed g_after_fault_isolation = self._get_subgraph_with_operational_lines(g_faulted, faulty_edge=faulty_edge) # --- Identify initial load loss --- initial_unserved_load_kw = 0 disconnected_load_nodes = {} # {node: load_kw} # Check connectivity for all load nodes for node_id, load_kw in self.loads_kw.items(): if load_kw <= 0: continue is_connected_to_source = False for cb_id, (src_node, start_node) in self.substation_connections.items(): if nx.has_path(g_after_fault_isolation, src_node, node_id): is_connected_to_source = True break if not is_connected_to_source: initial_unserved_load_kw += load_kw disconnected_load_nodes[node_id] = load_kw if initial_unserved_load_kw == 0: # Fault does not cause load loss (e.g. redundant line) detailed_fault_impacts.append({'fault': faulty_edge, 'unserved_kw_initial': 0, 'unserved_kw_final':0, 'restored_kw':0, 'risk_contrib':0}) continue # --- Attempt restoration via tie lines --- # This is a complex part. Needs to: # 1. Identify disconnected areas and loads. # 2. Identify available tie switches that can connect these areas to healthy feeders. # 3. Check capacity of tie lines and the supporting feeder. # 4. Prioritize restoration (e.g., maximize load restored). # For this model, a simplified restoration: # Iterate over available tie switches. If closing one helps, simulate it. # This should be greedy or more optimized. restored_load_kw_total_for_this_fault = 0 # Create a graph state for restoration attempts g_for_restoration = g_after_fault_isolation.copy() # Sort disconnected loads by size (optional, for prioritization) sorted_disconnected_loads = sorted(disconnected_load_nodes.items(), key=lambda item: item[1], reverse=True) # Try closing tie switches one by one (if they connect a live part to a dead part) # This is a very simplified greedy approach. # A proper approach would evaluate all combinations or use optimization. # Identify current live sources/feeders live_feeder_sources = [] # (cb_id, start_node_of_live_feeder) for cb_id, (src,start) in self.substation_connections.items(): if nx.has_path(g_for_restoration, src, start): # Check if substation itself is connected live_feeder_sources.append(start) for tie in self.tie_switches_info: tie_n1, tie_n2 = tie['nodes'] tie_capacity_a = tie['capacity_A'] if not g_for_restoration.has_node(tie_n1) or not g_for_restoration.has_node(tie_n2): continue if g_for_restoration.has_edge(tie_n1, tie_n2): continue # Already closed or part of main graph (should not be for ties) # Check if one end is live and other is dead (or part of the disconnected component) tie_n1_is_live = any(nx.has_path(g_for_restoration, src, tie_n1) for src in live_feeder_sources) tie_n2_is_live = any(nx.has_path(g_for_restoration, src, tie_n2) for src in live_feeder_sources) if tie_n1_is_live == tie_n2_is_live: continue # Both live or both dead, closing doesn't bridge outage for now live_tie_node, dead_tie_node = (tie_n1, tie_n2) if tie_n1_is_live else (tie_n2, tie_n1) # Check if dead_tie_node is part of the current outage we are trying to fix # This requires knowing which component dead_tie_node belongs to. # For now, assume if it's not live, it's part of some outage. # Simulate closing this tie switch g_for_restoration.add_edge(live_tie_node, dead_tie_node, id=tie['id'], type='tie_closed', rated_current_a=tie_capacity_a, resistance_ohm=0.001, length_km=0.01) # Check how much load can be restored through this tie without overloading tie or new path # This requires a flow calculation on g_for_restoration. # Simplified: Check loads now connected. newly_restored_load_kw_this_tie = 0 temp_restored_nodes_this_tie = [] for node_id, load_val in disconnected_load_nodes.items(): if node_id not in g_for_restoration: continue # Should not happen # Check if this node is now connected to ANY source is_now_connected = any(nx.has_path(g_for_restoration, src, node_id) for src in live_feeder_sources) if is_now_connected and node_id not in temp_restored_nodes_this_tie: # And not already counted as restored by previous ties # More checks needed: # 1. Tie line capacity: Power through (live_tie_node, dead_tie_node) <= tie_capacity_a # 2. Path capacity on the live feeder. # This is where the simplified flow becomes a bottleneck. # For now, assume if connected, it can be restored up to a certain limit. # This is a MAJOR simplification. newly_restored_load_kw_this_tie += load_val temp_restored_nodes_this_tie.append(node_id) # Here, we'd need to check if adding newly_restored_load_kw_this_tie overloads the tie or feeder. # If current_through_tie > tie_capacity_a, then not all of this load can be restored. # This part needs a proper constrained flow allocation. # For now, let's assume a fraction can be restored if connected, or all if small. # This is a placeholder for a more robust restoration algorithm. # Let's assume, for now, if connected, it's restored. If this overloads things, # the overload risk model should capture it (but that's for normal state). # Here, the goal is to minimize unserved load. # If this tie leads to overload, we shouldn't use it or only partially. # For now, naively accept all newly connected load. if newly_restored_load_kw_this_tie > 0: restored_load_kw_total_for_this_fault += newly_restored_load_kw_this_tie # Update disconnected_load_nodes: for r_node in temp_restored_nodes_this_tie: if r_node in disconnected_load_nodes: del disconnected_load_nodes[r_node] # No longer disconnected else: # Closing this tie didn't help, revert if g_for_restoration.has_edge(live_tie_node, dead_tie_node): g_for_restoration.remove_edge(live_tie_node, dead_tie_node) # Final unserved load for this fault scenario final_unserved_load_kw = initial_unserved_load_kw - restored_load_kw_total_for_this_fault final_unserved_load_kw = max(0, final_unserved_load_kw) # Cannot be negative consequence_c_loss = final_unserved_load_kw * COST_VOLL_PER_KW risk_contribution = prob_line_fault * consequence_c_loss total_load_loss_risk += risk_contribution detailed_fault_impacts.append({ 'fault_type': 'line', 'component_id': faulty_edge, 'prob_fault': prob_line_fault, 'unserved_kw_initial': initial_unserved_load_kw, 'restored_kw': restored_load_kw_total_for_this_fault, 'unserved_kw_final': final_unserved_load_kw, 'consequence_c_loss': consequence_c_loss, 'risk_contribution': risk_contribution }) # TODO: Add DG faults, Switch faults, User faults if they cause wider outages. # For DG faults: prob_dg_fault = FAILURE_RATE_DG_PERCENT # A DG fault primarily impacts system's ability to meet load or avoid overload. # It doesn't directly cause load loss unless it's islanded and the DG is the only source. # The problem implies grid-connected DGs. return total_load_loss_risk, detailed_fault_impacts # --- Main Execution --- if __name__ == '__main__': print("--- 配电网风险评估模型 Q1 ---") # 1. Load Data print("\n1. 加载数据...") loads = load_load_data() topology = load_topology_data() # print(f"负荷数据: {len(loads)} 点") # print(f"拓扑数据: {len(topology)} 条线路") # 2. Initialize Power Grid Model print("\n2. 初始化电网模型...") grid = PowerGridModel(loads, topology, DG_LOCATIONS_KW, TIE_SWITCHES_INFO, SUBSTATION_CONNECTIONS) # print(f"电网图: {grid.graph.number_of_nodes()} 个节点, {grid.graph.number_of_edges()} 条边") # print(f"馈线信息: {grid.feeder_info}") # --- 问题1: 失负荷风险和过负荷风险计算模型 --- print("\n--- 问题1: 风险计算 ---") # A. 过负荷风险模型 (R_over = P_over * C_over) # For Q1, DGs are at BASE_DG_CAPACITY_KW. This is a deterministic check for this state. # P_over = 1 if overload occurs, 0 otherwise. C_over is the penalty. print("\nA. 计算过负荷风险...") # Note: The calculate_power_flows_and_currents is highly simplified. # Results for overload depend heavily on its accuracy and line ratings. try: risk_overload, overloaded_lines_details, substation_p, final_dg_out = grid.calculate_overload_risk() print(f" 计算得到的过负荷风险 (R_over): ${risk_overload:.2f}") if overloaded_lines_details: print(f" 检测到过负荷线路 ({len(overloaded_lines_details)} 条):") # for ol in overloaded_lines_details[:3]: # Print first 3 # print(f" - 线路 {ol['edge']}, 电流: {ol['current']:.2f}A, 额定: {ol['rated']:.2f}A, 超出: {ol['over_by_%']:.2f}%") else: print(" 在当前DG配置下,未检测到线路过负荷。") # print(f" 变电站出口功率 (kW): {substation_p}") # print(f" 最终DG出力 (kW) (可能经过削减): {final_dg_out}") except Exception as e: print(f" 计算过负荷风险时发生错误: {e}") risk_overload = -1 # Indicate error # B. 失负荷风险模型 (R_loss = sum(P_fault_i * C_loss_i)) print("\nB. 计算失负荷风险...") # Note: Restoration logic is simplified. try: total_r_loss, fault_details = grid.calculate_load_loss_risk() print(f" 计算得到的总失负荷风险 (R_loss): ${total_r_loss:.2f} (基于所选成本)") # print("\n 部分故障场景详情:") # for fd in fault_details[:3]: # Print first 3 # print(f" - 故障线路: {fd.get('component_id')}, " # f"初始失负荷: {fd.get('unserved_kw_initial'):.2f} kW, " # f"最终失负荷: {fd.get('unserved_kw_final'):.2f} kW, " # f"风险贡献: ${fd.get('risk_contribution'):.2f}") except Exception as e: print(f" 计算失负荷风险时发生错误: {e}") total_r_loss = -1 # Indicate error print("\n--- 模型执行完毕 ---") print("注意: 此模型包含多项简化和假设 (如线路额定电流, 成本参数, 潮流计算简化, 恢复逻辑简化).") print("结果的准确性取决于这些假设的合理性和参数的精确性。") 对此代码进行分析
最新发布
05-12
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值