• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

connorivy / beamOS / 14882999397

07 May 2025 12:10PM UTC coverage: 70.971%. First build
14882999397

Pull #85

github

web-flow
Merge 16abd45d8 into e6bb4b15c
Pull Request #85: Add load combos

867 of 1830 branches covered (47.38%)

Branch coverage included in aggregate %.

1474 of 1639 new or added lines in 74 files covered. (89.93%)

7536 of 10010 relevant lines covered (75.28%)

0.78 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

0.0
/src/StructuralAnalysis/BeamOs.StructuralAnalysis.Domain/OpenSees/OpenSeesAnalysisModel.cs
1
using System.Diagnostics;
2
using System.Reflection;
3
using System.Runtime.InteropServices;
4
using BeamOs.StructuralAnalysis.Domain.AnalyticalResults.NodeResultAggregate;
5
using BeamOs.StructuralAnalysis.Domain.AnalyticalResults.ResultSetAggregate;
6
using BeamOs.StructuralAnalysis.Domain.Common;
7
using BeamOs.StructuralAnalysis.Domain.DirectStiffnessMethod;
8
using BeamOs.StructuralAnalysis.Domain.OpenSees.Tcp;
9
using BeamOs.StructuralAnalysis.Domain.PhysicalModel.LoadCombinations;
10
using BeamOs.StructuralAnalysis.Domain.PhysicalModel.ModelAggregate;
11
using Microsoft.Extensions.Logging;
12

13
namespace BeamOs.StructuralAnalysis.Domain.OpenSees;
14

15
public sealed class OpenSeesAnalysisModel(Model model, UnitSettings unitSettings, ILogger logger)
×
16
    : IDisposable
17
{
18
    private readonly TcpServer displacementServer = TcpServer.CreateStarted(logger);
×
19
    private readonly TcpServer reactionServer = TcpServer.CreateStarted(logger);
×
20
    private readonly TcpServer elementForceServer = TcpServer.CreateStarted(logger);
×
21

22
    public async Task<AnalysisResults> RunAnalysis(LoadCombination loadCombination)
23
    {
×
24
        TclWriter tclWriter = CreateWriterFromModel(
×
25
            model,
×
26
            this.displacementServer.Port,
×
27
            this.reactionServer.Port,
×
28
            this.elementForceServer.Port,
×
NEW
29
            loadCombination,
×
30
            unitSettings
×
31
        );
×
32

33
        if (tclWriter.OutputFileWithPath is null)
×
34
        {
×
35
            throw new Exception(
×
36
                "OutputFileWithPath should not be null after calling TclWriter.Write()"
×
37
            );
×
38
        }
39

40
        string outputDir = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location);
×
41
        await this.RunTclWithOpenSees(tclWriter.OutputFileWithPath, outputDir);
×
42

NEW
43
        var resultSet = new ResultSet(model.Id, loadCombination.Id)
×
NEW
44
        {
×
NEW
45
            NodeResults = this.GetResults(model, tclWriter),
×
NEW
46
        };
×
47

48
        var dsmElements =
×
49
            model.Settings.AnalysisSettings.Element1DAnalysisType == Element1dAnalysisType.Euler
×
50
                ? model.Element1ds.Select(el => new DsmElement1d(el)).ToArray()
×
51
                : model.Element1ds.Select(el => new TimoshenkoDsmElement1d(el)).ToArray();
×
52

53
        var otherResults = resultSet.ComputeDiagramsAndElement1dResults(dsmElements, unitSettings);
×
54

55
        return new AnalysisResults()
×
56
        {
×
57
            ResultSet = resultSet,
×
58
            OtherAnalyticalResults = otherResults,
×
59
        };
×
60
    }
×
61

62
    private static TclWriter CreateWriterFromModel(
63
        Model model,
64
        int displacementPort,
65
        int reactionPort,
66
        int elementForcesPort,
67
        LoadCombination loadCombination,
68
        UnitSettings? unitSettingsOverride = null
69
    )
70
    {
×
71
        TclWriter tclWriter = new(
×
72
            model.Settings,
×
73
            displacementPort,
×
74
            reactionPort,
×
75
            elementForcesPort,
×
76
            unitSettingsOverride
×
77
        );
×
78

79
        //this.element1dCache = new(model.Element1ds.Count);
80
        foreach (var element1d in model.Element1ds)
×
81
        {
×
82
            //this.element1dCache.Add(element1d.Id, element1d);
83
            tclWriter.AddHydratedElement(element1d);
×
84
        }
×
85

86
        Debug.Assert(model.PointLoads is not null);
×
87
        Debug.Assert(model.MomentLoads is not null);
×
NEW
88
        tclWriter.AddLoads(model.PointLoads, model.MomentLoads, loadCombination);
×
89

90
        tclWriter.DefineAnalysis();
×
91
        tclWriter.Write();
×
92

93
        return tclWriter;
×
94
    }
×
95

96
    private double[] displacements;
97
    private double[] reactions;
98

99
    //private double[] elemForces;
100

101
    private async Task RunTclWithOpenSees(string tclFileWithPath, string outputDir)
102
    {
×
103
        Task listenDisp = this.displacementServer.Listen(data => this.displacements = data);
×
104
        Task listenReact = this.reactionServer.Listen(data => this.reactions = data);
×
105
        //Task listenElemForces = this.elementForceServer.Listen(data => this.elemForces = data);
106

107
        string exePath;
108
        if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
×
109
        {
×
110
            exePath = Path.Combine(
×
111
                outputDir,
×
112
                "runtimes",
×
113
                "win-x64",
×
114
                "native",
×
115
                "bin",
×
116
                "OpenSees.exe"
×
117
            );
×
118
        }
×
119
        else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
×
120
        {
×
121
            if (Directory.Exists("/root/OpenSees/build/bin"))
×
122
            {
×
123
                // i don't know why, but the executable is much faster when built in the docker container
124
                exePath = "/root/OpenSees/build/bin/OpenSees";
×
125
            }
×
126
            else
127
            {
×
128
                exePath = Path.Combine(
×
129
                    outputDir,
×
130
                    "runtimes",
×
131
                    "linux-x64",
×
132
                    "native",
×
133
                    "bin",
×
134
                    "OpenSees"
×
135
                );
×
136
            }
×
137
        }
×
138
        else
139
        {
×
140
            throw new NotSupportedException("Unsupported OS platform");
×
141
        }
142

143
        if (!File.Exists(exePath))
×
144
        {
×
145
            throw new Exception($"OpenSees executable not found at {exePath}");
×
146
        }
147

148
        using Process process = new()
×
149
        {
×
150
            StartInfo = new ProcessStartInfo(exePath)
×
151
            {
×
152
                WorkingDirectory = outputDir,
×
153
                Arguments = tclFileWithPath,
×
154
                UseShellExecute = false,
×
155
                RedirectStandardOutput = true,
×
156
                RedirectStandardError = true,
×
157
            },
×
158
            EnableRaisingEvents = true,
×
159
        };
×
160
        process.OutputDataReceived += new DataReceivedEventHandler(this.process_OutputDataReceived);
×
161
        process.ErrorDataReceived += new DataReceivedEventHandler(this.process_ErrorDataReceived);
×
162

163
        try
164
        {
×
165
            // logger.LogInformation("Starting process");
166
            process.Start();
×
167
            // logger.LogInformation("Process started");
168
        }
×
169
        catch (System.ComponentModel.Win32Exception ex)
×
170
        {
×
171
            throw new Exception(
×
172
                "Unable to run opensees.exe. Did you follow the instructions in beamOS/opensees/readme?",
×
173
                ex
×
174
            );
×
175
        }
176
        process.BeginErrorReadLine();
×
177
        process.BeginOutputReadLine();
×
178

179
        // todo : failure policy
180
        await Task.WhenAny(Task.WhenAll(listenDisp, listenReact), Task.Delay(15000));
×
181

182
        if (listenDisp.Status != TaskStatus.RanToCompletion)
×
183
        {
×
184
            logger.LogError(
×
185
                "Unable to receive the node displacements from OpenSees within the timeframe"
×
186
            );
×
187
        }
×
188
        if (listenReact.Status != TaskStatus.RanToCompletion)
×
189
        {
×
190
            logger.LogError(
×
191
                "Unable to receive the node reactions from OpenSees within the timeframe"
×
192
            );
×
193
        }
×
194
        //if (listenElemForces.Status != TaskStatus.RanToCompletion)
195
        //{
196
        //    logger.LogError(
197
        //        "Unable to receive the local element forces from OpenSees within the timeframe"
198
        //    );
199
        //}
200

201
        //await listenDisp;
202
        //await listenReact;
203
        //await listenElemForces;
204
        process.Kill();
×
205
        //await TcpServerCallback.Result;
206
    }
×
207

208
    //private AnalyticalResults GetResults(Model model, TclWriter tclWriter)
209
    private NodeResult[] GetResults(Model model, TclWriter tclWriter)
210
    {
×
211
        ResultSetId analyticalResultsId = new();
×
212

213
        NodeResult[] nodeResults = new NodeResult[this.displacements.Length / 6];
×
214
        for (int i = 0; i < this.displacements.Length / 6; i++)
×
215
        {
×
216
            int indexOffset = i * 6;
×
217
            nodeResults[i] = new NodeResult(
×
218
                model.Id,
×
219
                analyticalResultsId,
×
220
                tclWriter.GetNodeIdFromOutputIndex(i),
×
221
                new Forces(
×
222
                    this.reactions[indexOffset],
×
223
                    this.reactions[indexOffset + 1],
×
224
                    this.reactions[indexOffset + 2],
×
225
                    this.reactions[indexOffset + 3],
×
226
                    this.reactions[indexOffset + 4],
×
227
                    this.reactions[indexOffset + 5],
×
228
                    unitSettings.ForceUnit,
×
229
                    unitSettings.TorqueUnit
×
230
                ),
×
231
                new Displacements(
×
232
                    this.displacements[indexOffset],
×
233
                    this.displacements[indexOffset + 1],
×
234
                    this.displacements[indexOffset + 2],
×
235
                    this.displacements[indexOffset + 3],
×
236
                    this.displacements[indexOffset + 4],
×
237
                    this.displacements[indexOffset + 5],
×
238
                    unitSettings.LengthUnit,
×
239
                    unitSettings.AngleUnit
×
240
                )
×
241
            );
×
242
        }
×
243

244
        return nodeResults;
×
245
    }
×
246

NEW
247
    private static readonly HashSet<string> ignoredErrorMessages = new()
×
NEW
248
    {
×
NEW
249
        "         OpenSees -- Open System For Earthquake Engineering Simulation",
×
NEW
250
        "                 Pacific Earthquake Engineering Research Center",
×
NEW
251
        "                        Version 3.8.0 64-Bit",
×
NEW
252
        "      (c) Copyright 1999-2016 The Regents of the University of California",
×
NEW
253
        "                              All Rights Reserved",
×
NEW
254
        "  (Copyright and Disclaimer @ http://www.berkeley.edu/OpenSees/copyright.html)",
×
NEW
255
    };
×
256

257
    void process_ErrorDataReceived(object sender, DataReceivedEventArgs e)
258
    {
×
NEW
259
        if (string.IsNullOrEmpty(e.Data) || ignoredErrorMessages.Contains(e.Data))
×
NEW
260
        {
×
NEW
261
            return;
×
262
        }
263

264
        logger.LogError("OpenSees process error {data}", e.Data);
×
265
    }
×
266

267
    void process_OutputDataReceived(object sender, DataReceivedEventArgs e)
268
    {
×
269
        logger.LogInformation("OpenSees process info {data}", e.Data);
×
270
    }
×
271

272
    public void Dispose()
273
    {
×
274
        this.displacementServer.Dispose();
×
275
        this.reactionServer.Dispose();
×
276
        this.elementForceServer.Dispose();
×
277
    }
×
278
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc