848 lines
No EOL
28 KiB
MQL5
848 lines
No EOL
28 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| OMV Model.mqh |
|
|
//| Copyright 2026, Allan Munene Mutiiria. |
|
|
//| https://t.me/Forex_Algo_Trader |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2026, Allan Munene Mutiiria."
|
|
#property link "https://t.me/Forex_Algo_Trader"
|
|
|
|
#ifndef OMV_MODEL_MQH
|
|
#define OMV_MODEL_MQH
|
|
|
|
#include "OMV Protobuf.mqh"
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Capacity limits |
|
|
//+------------------------------------------------------------------+
|
|
#define OMV_MAX_NODES 512 // Layers the graph may hold
|
|
#define OMV_MAX_TENSORS 512 // Weight tensors the graph may hold
|
|
#define OMV_MAX_PORTS 8 // Names one node may carry per side
|
|
#define OMV_MAX_DIMS 8 // Axes one tensor may have
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Graph layer |
|
|
//+------------------------------------------------------------------+
|
|
struct OmvNode
|
|
{
|
|
// Name the operator this layer applies
|
|
string op;
|
|
// Name this layer was given
|
|
string name;
|
|
// List the names feeding this layer
|
|
string feeds[OMV_MAX_PORTS];
|
|
// Count the names present
|
|
int feedCount;
|
|
// List the names this layer produces
|
|
string emits[OMV_MAX_PORTS];
|
|
// Count the names present
|
|
int emitCount;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Weight tensor |
|
|
//+------------------------------------------------------------------+
|
|
struct OmvTensor
|
|
{
|
|
// Name the nodes refer to it by
|
|
string name;
|
|
// Measure each axis
|
|
int dim[OMV_MAX_DIMS];
|
|
// Count the axes present
|
|
int dims;
|
|
// Record the ONNX type code
|
|
int dataType;
|
|
// Multiply every axis to size the block
|
|
int values;
|
|
// Track the smallest weight held
|
|
double minValue;
|
|
// Track the largest weight held
|
|
double maxValue;
|
|
// Track the average weight
|
|
double meanValue;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Exposed graph port |
|
|
//+------------------------------------------------------------------+
|
|
struct OmvPort
|
|
{
|
|
// Name the graph exposes
|
|
string name;
|
|
// Measure each axis
|
|
int dim[OMV_MAX_DIMS];
|
|
// Count the axes present
|
|
int dims;
|
|
// Record the ONNX type code
|
|
int elemType;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| ONNX file reader |
|
|
//+------------------------------------------------------------------+
|
|
class COmvModel
|
|
{
|
|
private:
|
|
// Store the layers in file order
|
|
OmvNode m_node[];
|
|
// Store the weight tensors
|
|
OmvTensor m_tensor[];
|
|
// Store the graph inputs
|
|
OmvPort m_input[];
|
|
// Store the graph outputs
|
|
OmvPort m_output[];
|
|
// Count the layers held
|
|
int m_nodes;
|
|
// Count the tensors held
|
|
int m_tensors;
|
|
// Count the graph inputs held
|
|
int m_inputs;
|
|
// Count the graph outputs held
|
|
int m_outputs;
|
|
// Record the format revision
|
|
long m_irVersion;
|
|
// Record the operator set version
|
|
int m_opset;
|
|
// Name the tool that wrote the file
|
|
string m_producer;
|
|
// Name the graph inside the file
|
|
string m_graphName;
|
|
// Remember the file that was read
|
|
string m_path;
|
|
// Explain why the last load failed
|
|
string m_error;
|
|
|
|
bool ParseGraph(const uchar &raw[], const int from, const int to);
|
|
bool ParseNode(const uchar &raw[], const int from, const int to);
|
|
bool ParseTensor(const uchar &raw[], const int from, const int to);
|
|
bool ParseSparse(const uchar &raw[], const int from, const int to);
|
|
bool ParsePort(const uchar &raw[], const int from, const int to, OmvPort &port);
|
|
void TensorStats(const uchar &raw[], const int from, const int to, OmvTensor &tensor);
|
|
void PackedStats(const uchar &raw[], const int from, const int to,
|
|
const int field, OmvTensor &tensor);
|
|
|
|
public:
|
|
COmvModel(void);
|
|
bool Load(const string path);
|
|
void Clear(void);
|
|
|
|
int Nodes(void) const { return m_nodes; }
|
|
int Tensors(void) const { return m_tensors; }
|
|
int Inputs(void) const { return m_inputs; }
|
|
int Outputs(void) const { return m_outputs; }
|
|
long IrVersion(void) const { return m_irVersion; }
|
|
int Opset(void) const { return m_opset; }
|
|
string Producer(void) const { return m_producer; }
|
|
string GraphName(void) const { return m_graphName; }
|
|
string Path(void) const { return m_path; }
|
|
string Error(void) const { return m_error; }
|
|
int Parameters(void) const;
|
|
|
|
void NodeAt(const int i, OmvNode &out) const { if(i >= 0 && i < m_nodes) out = m_node[i]; }
|
|
void TensorAt(const int i, OmvTensor &out) const { if(i >= 0 && i < m_tensors) out = m_tensor[i]; }
|
|
void InputAt(const int i, OmvPort &out) const { if(i >= 0 && i < m_inputs) out = m_input[i]; }
|
|
void OutputAt(const int i, OmvPort &out) const { if(i >= 0 && i < m_outputs) out = m_output[i]; }
|
|
string DimsText(const int &dim[], const int dims) const;
|
|
string TypeName(const int code) const;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Construct an empty model |
|
|
//+------------------------------------------------------------------+
|
|
COmvModel::COmvModel(void)
|
|
{
|
|
//--- Clear every field so a query before loading reports empty
|
|
Clear();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Drop everything the previous file filled |
|
|
//+------------------------------------------------------------------+
|
|
void COmvModel::Clear(void)
|
|
{
|
|
//--- Zero the counts that bound every array
|
|
m_nodes = 0;
|
|
m_tensors = 0;
|
|
m_inputs = 0;
|
|
m_outputs = 0;
|
|
//--- Release the stored structures
|
|
ArrayResize(m_node, 0);
|
|
ArrayResize(m_tensor, 0);
|
|
ArrayResize(m_input, 0);
|
|
ArrayResize(m_output, 0);
|
|
//--- Clear the header fields
|
|
m_irVersion = 0;
|
|
m_opset = 0;
|
|
m_producer = "";
|
|
m_graphName = "";
|
|
m_path = "";
|
|
m_error = "";
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Walk raw weight bytes for their range and average |
|
|
//+------------------------------------------------------------------+
|
|
void COmvModel::TensorStats(const uchar &raw[], const int from, const int to, OmvTensor &tensor)
|
|
{
|
|
//--- Take eight bytes per value for a double and four otherwise
|
|
const int width = (tensor.dataType == 11) ? 8 : 4;
|
|
//--- Count the whole values the payload holds
|
|
const int count = (to - from) / width;
|
|
//--- Abort when the payload holds nothing
|
|
if(count <= 0)
|
|
return;
|
|
//--- Point a cursor at the payload
|
|
CPbReader bytes;
|
|
if(!bytes.Attach(raw, from, to))
|
|
return;
|
|
//--- Start the running range and total
|
|
double lowest = 0.0, highest = 0.0, total = 0.0;
|
|
//--- Visit every stored value
|
|
for(int i = 0; i < count; i++)
|
|
{
|
|
//--- Read the value at the stored width
|
|
const double v = (width == 8) ? bytes.ReadDouble() : (double)bytes.ReadFloat();
|
|
//--- Stop on a broken read
|
|
if(bytes.Failed())
|
|
break;
|
|
//--- Seed the range from the first value
|
|
if(i == 0)
|
|
{
|
|
lowest = v;
|
|
highest = v;
|
|
}
|
|
//--- Lower the floor when this value is smaller
|
|
if(v < lowest)
|
|
lowest = v;
|
|
//--- Raise the ceiling when this value is larger
|
|
if(v > highest)
|
|
highest = v;
|
|
//--- Add the value to the running total
|
|
total += v;
|
|
}
|
|
//--- Publish the range and the average
|
|
tensor.minValue = lowest;
|
|
tensor.maxValue = highest;
|
|
tensor.meanValue = total / count;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Walk a typed payload the file used instead of raw bytes |
|
|
//+------------------------------------------------------------------+
|
|
void COmvModel::PackedStats(const uchar &raw[], const int from, const int to,
|
|
const int field, OmvTensor &tensor)
|
|
{
|
|
//--- Point a cursor at the payload
|
|
CPbReader bytes;
|
|
if(!bytes.Attach(raw, from, to))
|
|
return;
|
|
//--- Start the running range and total
|
|
double lowest = 0.0, highest = 0.0, total = 0.0;
|
|
int seen = 0;
|
|
//--- Read the payload the way the field it arrived in is written
|
|
while(!bytes.Eof())
|
|
{
|
|
//--- Take the next value in the packed run
|
|
double v = 0.0;
|
|
if(field == 4)
|
|
v = (double)bytes.ReadFloat();
|
|
else
|
|
if(field == 10)
|
|
v = bytes.ReadDouble();
|
|
else
|
|
v = (double)(long)bytes.ReadVarint();
|
|
//--- Stop on a broken read
|
|
if(bytes.Failed())
|
|
break;
|
|
//--- Seed the range from the first value
|
|
if(seen == 0)
|
|
{
|
|
lowest = v;
|
|
highest = v;
|
|
}
|
|
//--- Lower the floor when this value is smaller
|
|
if(v < lowest)
|
|
lowest = v;
|
|
//--- Raise the ceiling when this value is larger
|
|
if(v > highest)
|
|
highest = v;
|
|
//--- Add the value to the running total
|
|
total += v;
|
|
seen++;
|
|
}
|
|
//--- Abort when the read stopped before any value arrived
|
|
if(seen <= 0)
|
|
return;
|
|
//--- Publish the range and the average
|
|
tensor.minValue = lowest;
|
|
tensor.maxValue = highest;
|
|
tensor.meanValue = total / seen;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Read one node into a layer |
|
|
//+------------------------------------------------------------------+
|
|
bool COmvModel::ParseNode(const uchar &raw[], const int from, const int to)
|
|
{
|
|
//--- Refuse a file that exceeds the node limit
|
|
if(m_nodes >= OMV_MAX_NODES)
|
|
return false;
|
|
//--- Point a cursor at this node
|
|
CPbReader reader;
|
|
if(!reader.Attach(raw, from, to))
|
|
return false;
|
|
//--- Start an empty layer
|
|
OmvNode layer;
|
|
layer.op = "";
|
|
layer.name = "";
|
|
layer.feedCount = 0;
|
|
layer.emitCount = 0;
|
|
//--- Read every field of the node
|
|
while(!reader.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int field, wire;
|
|
if(!reader.ReadTag(field, wire))
|
|
break;
|
|
//--- Skip anything that is not text
|
|
if(wire != PB_LENGTH)
|
|
{
|
|
if(!reader.SkipField(wire))
|
|
break;
|
|
continue;
|
|
}
|
|
//--- Read the field as text
|
|
string text;
|
|
if(!reader.ReadString(text))
|
|
break;
|
|
//--- Store an input name
|
|
if(field == 1 && layer.feedCount < OMV_MAX_PORTS)
|
|
layer.feeds[layer.feedCount++] = text;
|
|
//--- Store an output name
|
|
else if(field == 2 && layer.emitCount < OMV_MAX_PORTS)
|
|
layer.emits[layer.emitCount++] = text;
|
|
//--- Store the layer name
|
|
else if(field == 3)
|
|
layer.name = text;
|
|
//--- Store the operator
|
|
else if(field == 4)
|
|
layer.op = text;
|
|
}
|
|
//--- Reject a node carrying no operator
|
|
if(StringLen(layer.op) == 0)
|
|
return false;
|
|
//--- Append the finished layer
|
|
ArrayResize(m_node, m_nodes + 1);
|
|
m_node[m_nodes++] = layer;
|
|
//--- Report success
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Read one initializer into a weight tensor |
|
|
//+------------------------------------------------------------------+
|
|
bool COmvModel::ParseTensor(const uchar &raw[], const int from, const int to)
|
|
{
|
|
//--- Refuse a file that exceeds the tensor limit
|
|
if(m_tensors >= OMV_MAX_TENSORS)
|
|
return false;
|
|
//--- Point a cursor at this tensor
|
|
CPbReader reader;
|
|
if(!reader.Attach(raw, from, to))
|
|
return false;
|
|
//--- Keep the slice so nested payloads can be located
|
|
uchar inner[];
|
|
reader.Bytes(inner);
|
|
//--- Start an empty tensor
|
|
OmvTensor weights;
|
|
weights.name = "";
|
|
weights.dims = 0;
|
|
weights.dataType = 0;
|
|
weights.values = 0;
|
|
weights.minValue = 0.0;
|
|
weights.maxValue = 0.0;
|
|
weights.meanValue = 0.0;
|
|
//--- Mark both places a tensor may keep its weights as not yet found
|
|
int rawFrom = -1, rawTo = -1;
|
|
int packFrom = -1, packTo = -1, packField = 0;
|
|
//--- Read every field of the tensor
|
|
while(!reader.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int field, wire;
|
|
if(!reader.ReadTag(field, wire))
|
|
break;
|
|
//--- Store one axis length
|
|
if(field == 1 && wire == PB_VARINT)
|
|
{
|
|
//--- Keep the axis while room remains
|
|
if(weights.dims < OMV_MAX_DIMS)
|
|
weights.dim[weights.dims++] = (int)reader.ReadVarint();
|
|
else
|
|
reader.ReadVarint();
|
|
}
|
|
//--- Store several axis lengths packed together
|
|
else if(field == 1 && wire == PB_LENGTH)
|
|
{
|
|
//--- Locate the packed block
|
|
int f2, t2;
|
|
if(!reader.ReadBlock(f2, t2))
|
|
break;
|
|
//--- Read every length the block holds
|
|
CPbReader packed;
|
|
packed.Attach(inner, f2, t2);
|
|
while(!packed.Eof() && weights.dims < OMV_MAX_DIMS)
|
|
weights.dim[weights.dims++] = (int)packed.ReadVarint();
|
|
}
|
|
//--- Store the element type
|
|
else if(field == 2 && wire == PB_VARINT)
|
|
weights.dataType = (int)reader.ReadVarint();
|
|
//--- Store the tensor name
|
|
else if(field == 8 && wire == PB_LENGTH)
|
|
reader.ReadString(weights.name);
|
|
//--- Locate the raw weight payload
|
|
else if(field == 9 && wire == PB_LENGTH)
|
|
{
|
|
if(!reader.ReadBlock(rawFrom, rawTo))
|
|
break;
|
|
}
|
|
//--- Locate a typed payload, which the file may use instead
|
|
else if(wire == PB_LENGTH &&
|
|
(field == 4 || field == 5 || field == 7 || field == 10 || field == 11))
|
|
{
|
|
//--- Remember which typed field carried the weights
|
|
if(!reader.ReadBlock(packFrom, packTo))
|
|
break;
|
|
packField = field;
|
|
}
|
|
//--- Step over anything else
|
|
else if(!reader.SkipField(wire))
|
|
break;
|
|
}
|
|
//--- Multiply every axis to size the block
|
|
weights.values = (weights.dims > 0) ? 1 : 0;
|
|
for(int i = 0; i < weights.dims; i++)
|
|
weights.values *= weights.dim[i];
|
|
//--- Summarize whichever payload the file actually used
|
|
if(rawFrom >= 0 && (weights.dataType == 1 || weights.dataType == 11))
|
|
TensorStats(inner, rawFrom, rawTo, weights);
|
|
else
|
|
if(packFrom >= 0)
|
|
PackedStats(inner, packFrom, packTo, packField, weights);
|
|
//--- Reject a tensor carrying no name
|
|
if(StringLen(weights.name) == 0)
|
|
return false;
|
|
//--- Append the finished tensor
|
|
ArrayResize(m_tensor, m_tensors + 1);
|
|
m_tensor[m_tensors++] = weights;
|
|
//--- Report success
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Read a sparse initializer through its values tensor |
|
|
//+------------------------------------------------------------------+
|
|
bool COmvModel::ParseSparse(const uchar &raw[], const int from, const int to)
|
|
{
|
|
//--- Point a cursor at the sparse tensor
|
|
CPbReader reader;
|
|
if(!reader.Attach(raw, from, to))
|
|
return false;
|
|
//--- Keep the slice so the values tensor can be located
|
|
uchar inner[];
|
|
reader.Bytes(inner);
|
|
//--- Remember where the values sit
|
|
int valFrom = -1, valTo = -1;
|
|
//--- Read every field of the sparse tensor
|
|
while(!reader.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int field, wire;
|
|
if(!reader.ReadTag(field, wire))
|
|
break;
|
|
//--- Locate the tensor holding the values themselves
|
|
if(field == 1 && wire == PB_LENGTH)
|
|
{
|
|
if(!reader.ReadBlock(valFrom, valTo))
|
|
break;
|
|
}
|
|
//--- Step over the indices, the shape and anything else
|
|
else
|
|
if(!reader.SkipField(wire))
|
|
break;
|
|
}
|
|
//--- Nothing to report without the values tensor
|
|
if(valFrom < 0)
|
|
return false;
|
|
//--- Read the values as an ordinary tensor
|
|
return ParseTensor(inner, valFrom, valTo);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Read one value info into an exposed port |
|
|
//+------------------------------------------------------------------+
|
|
bool COmvModel::ParsePort(const uchar &raw[], const int from, const int to, OmvPort &port)
|
|
{
|
|
//--- Start an empty port
|
|
port.name = "";
|
|
port.dims = 0;
|
|
port.elemType = 0;
|
|
//--- Point a cursor at this value info
|
|
CPbReader value;
|
|
if(!value.Attach(raw, from, to))
|
|
return false;
|
|
//--- Keep the slice so nested payloads can be located
|
|
uchar level1[];
|
|
value.Bytes(level1);
|
|
//--- Read every field of the value info
|
|
while(!value.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int field, wire;
|
|
if(!value.ReadTag(field, wire))
|
|
break;
|
|
//--- Store the port name
|
|
if(field == 1 && wire == PB_LENGTH)
|
|
{
|
|
value.ReadString(port.name);
|
|
continue;
|
|
}
|
|
//--- Step over anything but the type description
|
|
if(field != 2 || wire != PB_LENGTH)
|
|
{
|
|
if(!value.SkipField(wire))
|
|
break;
|
|
continue;
|
|
}
|
|
//--- Descend into the type description
|
|
int t1, t2;
|
|
if(!value.ReadBlock(t1, t2))
|
|
break;
|
|
CPbReader type;
|
|
type.Attach(level1, t1, t2);
|
|
uchar level2[];
|
|
type.Bytes(level2);
|
|
//--- Read every field of the type
|
|
while(!type.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int f2, w2;
|
|
if(!type.ReadTag(f2, w2))
|
|
break;
|
|
//--- Step over anything but the tensor description
|
|
if(f2 != 1 || w2 != PB_LENGTH)
|
|
{
|
|
if(!type.SkipField(w2))
|
|
break;
|
|
continue;
|
|
}
|
|
//--- Descend into the tensor description
|
|
int s1, s2;
|
|
if(!type.ReadBlock(s1, s2))
|
|
break;
|
|
CPbReader tt;
|
|
tt.Attach(level2, s1, s2);
|
|
uchar level3[];
|
|
tt.Bytes(level3);
|
|
//--- Read every field of the tensor description
|
|
while(!tt.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int f3, w3;
|
|
if(!tt.ReadTag(f3, w3))
|
|
break;
|
|
//--- Store the element type
|
|
if(f3 == 1 && w3 == PB_VARINT)
|
|
{
|
|
port.elemType = (int)tt.ReadVarint();
|
|
continue;
|
|
}
|
|
//--- Step over anything but the shape
|
|
if(f3 != 2 || w3 != PB_LENGTH)
|
|
{
|
|
if(!tt.SkipField(w3))
|
|
break;
|
|
continue;
|
|
}
|
|
//--- Descend into the shape
|
|
int p1, p2;
|
|
if(!tt.ReadBlock(p1, p2))
|
|
break;
|
|
CPbReader shape;
|
|
shape.Attach(level3, p1, p2);
|
|
uchar level4[];
|
|
shape.Bytes(level4);
|
|
//--- Read every axis the shape holds
|
|
while(!shape.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int f4, w4;
|
|
if(!shape.ReadTag(f4, w4))
|
|
break;
|
|
//--- Step over anything but an axis
|
|
if(f4 != 1 || w4 != PB_LENGTH)
|
|
{
|
|
if(!shape.SkipField(w4))
|
|
break;
|
|
continue;
|
|
}
|
|
//--- Descend into the axis
|
|
int d1, d2;
|
|
if(!shape.ReadBlock(d1, d2))
|
|
break;
|
|
CPbReader dim;
|
|
dim.Attach(level4, d1, d2);
|
|
//--- Read every field of the axis
|
|
while(!dim.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int f5, w5;
|
|
if(!dim.ReadTag(f5, w5))
|
|
break;
|
|
//--- Store the axis length
|
|
if(f5 == 1 && w5 == PB_VARINT)
|
|
{
|
|
if(port.dims < OMV_MAX_DIMS)
|
|
port.dim[port.dims++] = (int)dim.ReadVarint();
|
|
else
|
|
dim.ReadVarint();
|
|
}
|
|
//--- Step over a named axis
|
|
else if(!dim.SkipField(w5))
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
//--- Report a port that carries a name
|
|
return StringLen(port.name) > 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Read the graph into nodes, weights and ports |
|
|
//+------------------------------------------------------------------+
|
|
bool COmvModel::ParseGraph(const uchar &raw[], const int from, const int to)
|
|
{
|
|
//--- Point a cursor at the graph
|
|
CPbReader graph;
|
|
if(!graph.Attach(raw, from, to))
|
|
{
|
|
m_error = "graph out of range";
|
|
return false;
|
|
}
|
|
//--- Keep the slice so nested payloads can be located
|
|
uchar inner[];
|
|
graph.Bytes(inner);
|
|
//--- Read every field of the graph
|
|
while(!graph.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int field, wire;
|
|
if(!graph.ReadTag(field, wire))
|
|
break;
|
|
//--- Step over anything that is not a nested message
|
|
if(wire != PB_LENGTH && field != 0)
|
|
{
|
|
if(!graph.SkipField(wire))
|
|
break;
|
|
continue;
|
|
}
|
|
//--- Locate the nested payload
|
|
int f2, t2;
|
|
if(!graph.ReadBlock(f2, t2))
|
|
break;
|
|
//--- Read a node
|
|
if(field == 1)
|
|
ParseNode(inner, f2, t2);
|
|
//--- Read an initializer
|
|
else if(field == 5)
|
|
ParseTensor(inner, f2, t2);
|
|
//--- Read a sparse initializer, whose values sit one level deeper
|
|
else if(field == 15)
|
|
ParseSparse(inner, f2, t2);
|
|
//--- Decode the graph name from its payload
|
|
else if(field == 2)
|
|
{
|
|
uchar tmp[];
|
|
ArrayResize(tmp, t2 - f2);
|
|
for(int i = f2; i < t2; i++)
|
|
tmp[i - f2] = inner[i];
|
|
m_graphName = CharArrayToString(tmp, 0, t2 - f2, CP_UTF8);
|
|
}
|
|
//--- Read a graph input
|
|
else if(field == 11)
|
|
{
|
|
OmvPort port;
|
|
if(ParsePort(inner, f2, t2, port))
|
|
{
|
|
ArrayResize(m_input, m_inputs + 1);
|
|
m_input[m_inputs++] = port;
|
|
}
|
|
}
|
|
//--- Read a graph output
|
|
else if(field == 12)
|
|
{
|
|
OmvPort port;
|
|
if(ParsePort(inner, f2, t2, port))
|
|
{
|
|
ArrayResize(m_output, m_outputs + 1);
|
|
m_output[m_outputs++] = port;
|
|
}
|
|
}
|
|
}
|
|
//--- Report success
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Open a file and read its whole structure |
|
|
//+------------------------------------------------------------------+
|
|
bool COmvModel::Load(const string path)
|
|
{
|
|
//--- Drop any model held from a previous call
|
|
Clear();
|
|
//--- Remember which file is being read
|
|
m_path = path;
|
|
//--- Read the whole file into memory
|
|
CPbReader reader;
|
|
if(!reader.LoadFile(path))
|
|
{
|
|
m_error = "cannot open " + path;
|
|
return false;
|
|
}
|
|
//--- Keep the bytes so nested payloads can be located
|
|
uchar raw[];
|
|
reader.Bytes(raw);
|
|
//--- Read every field of the model
|
|
while(!reader.Eof())
|
|
{
|
|
//--- Take the next field tag
|
|
int field, wire;
|
|
if(!reader.ReadTag(field, wire))
|
|
break;
|
|
//--- Store the format revision
|
|
if(field == 1 && wire == PB_VARINT)
|
|
m_irVersion = (long)reader.ReadVarint();
|
|
//--- Store the producing tool
|
|
else if(field == 2 && wire == PB_LENGTH)
|
|
reader.ReadString(m_producer);
|
|
//--- Read the graph itself
|
|
else if(field == 7 && wire == PB_LENGTH)
|
|
{
|
|
int from, to;
|
|
if(!reader.ReadBlock(from, to))
|
|
break;
|
|
if(!ParseGraph(raw, from, to))
|
|
return false;
|
|
}
|
|
//--- Read the operator set version
|
|
else if(field == 8 && wire == PB_LENGTH)
|
|
{
|
|
//--- Locate the operator set message
|
|
int from, to;
|
|
if(!reader.ReadBlock(from, to))
|
|
break;
|
|
//--- Read its version field
|
|
CPbReader ops;
|
|
ops.Attach(raw, from, to);
|
|
while(!ops.Eof())
|
|
{
|
|
int f2, w2;
|
|
if(!ops.ReadTag(f2, w2))
|
|
break;
|
|
if(f2 == 2 && w2 == PB_VARINT)
|
|
m_opset = (int)ops.ReadVarint();
|
|
else
|
|
if(!ops.SkipField(w2))
|
|
break;
|
|
}
|
|
}
|
|
//--- Step over anything else
|
|
else if(!reader.SkipField(wire))
|
|
break;
|
|
}
|
|
//--- Report a file that could not be decoded
|
|
if(reader.Failed() && m_nodes == 0)
|
|
{
|
|
m_error = "malformed protobuf in " + path;
|
|
return false;
|
|
}
|
|
//--- Report a file that holds no graph
|
|
if(m_nodes == 0)
|
|
{
|
|
m_error = "no nodes found in " + path;
|
|
return false;
|
|
}
|
|
//--- Report success
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Total every weight the file carries |
|
|
//+------------------------------------------------------------------+
|
|
int COmvModel::Parameters(void) const
|
|
{
|
|
//--- Add the value count of every tensor
|
|
int total = 0;
|
|
for(int i = 0; i < m_tensors; i++)
|
|
total += m_tensor[i].values;
|
|
//--- Return the running total
|
|
return total;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Render a shape as a bracketed list |
|
|
//+------------------------------------------------------------------+
|
|
string COmvModel::DimsText(const int &dim[], const int dims) const
|
|
{
|
|
//--- Print empty brackets for an unshaped tensor
|
|
if(dims <= 0)
|
|
return "[]";
|
|
//--- Join every axis with a separator
|
|
string out = "[";
|
|
for(int i = 0; i < dims; i++)
|
|
{
|
|
//--- Separate all but the first axis
|
|
if(i > 0)
|
|
out += "x";
|
|
//--- Append this axis length
|
|
out += IntegerToString(dim[i]);
|
|
}
|
|
//--- Close the bracket
|
|
return out + "]";
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Name an ONNX element type code |
|
|
//+------------------------------------------------------------------+
|
|
string COmvModel::TypeName(const int code) const
|
|
{
|
|
//--- Match the numeric types this viewer expects to meet
|
|
switch(code)
|
|
{
|
|
case 1:
|
|
return "float";
|
|
case 2:
|
|
return "uint8";
|
|
case 3:
|
|
return "int8";
|
|
case 6:
|
|
return "int32";
|
|
case 7:
|
|
return "int64";
|
|
case 8:
|
|
return "string";
|
|
case 9:
|
|
return "bool";
|
|
case 10:
|
|
return "float16";
|
|
case 11:
|
|
return "double";
|
|
}
|
|
//--- Print the raw code for anything unrecognized
|
|
return "type" + IntegerToString(code);
|
|
}
|
|
|
|
#endif // OMV_MODEL_MQH
|
|
//+------------------------------------------------------------------+ |