Operating on inputs before list matching to prevent duplicate calculations in C#

Hi,

I am working on a component in C# in Visual Studio and am running to a bit of an optimization problem I don’t quite know how to approach. The component runs an operation on a brep input with item access - the subtractFrom input in the code snippet below - with a set of breps with list access - the subtractWith input. I am trying to improve compute times when working with very large data sets, specifically when the breps with list access stay the same but I feed in more than one brep into the subtractFrom input with item access, which I believe causes grasshopper’s automatic list matching to duplicate some calculations unnecessarily.

Specifically what I am trying to improve is when I am converting the list of subtractWith breps to my custom class BrepBB in the solve instance in the code below. The constructor that I am using calculates a bounding box for the brep and holds it alongside the brep so that I don’t have to compute it over and over later in the script. That in itself isn’t a problem, but the problem is that when I am inputting a list into subtractFrom which I’ve only given item access, the BrepBB constructor that is being fed the data from subtractWith is being duplicated for every item in the subtractFrom list. If the subtractFrom input has 1000 items in it, I beileive the constructor is being run 999 more times than necessary due to longest list data matching or similar.

What I would like to do instead is have the breps converted to my BrepBB class immediately on input and before any longest list matching or the like is done in the component so that the constructor can just run a single time per data input. I considered giving the subtractFrom data list access, but I am running the code multi-threaded and that would put all of the calculations onto a single thread with how it is currenlty opperating.

I hope all of that made sense. If anyone know how this is done, It would be greatly appreciated!

    protected override void RegisterInputParams(GH_Component.GH_InputParamManager pManager)
    {
        pManager.AddBrepParameter("Subtract From", "A", "Set to subtract from.", GH_ParamAccess.item);
        pManager.AddBrepParameter("Subtract With", "B", "Set to subtract with.", GH_ParamAccess.list);
        pManager.AddNumberParameter("Tolerance", "T", "Subtraction tolerance", GH_ParamAccess.item, DocumentTolerance());
        pManager.AddBooleanParameter("Strict", "S", "True to check for true interesection, false to only check bounding boxes", GH_ParamAccess.item, false);
        pManager.AddBooleanParameter("Accurate Bounding", "C", "true to compute accurate bounding boxes", GH_ParamAccess.item, false);
    }

    /// <summary>
    /// Registers all the output parameters for this component.
    /// </summary>
    protected override void RegisterOutputParams(GH_Component.GH_OutputParamManager pManager)
    {
        pManager.AddBrepParameter("Result", "R", "Subtraction result.", GH_ParamAccess.list);
    }

    protected override void SolveInstance(IGH_DataAccess DA)
    {

        if (InPreSolve)
        {
            //Fist Pass; collect input data
            Brep subtractFromTemp = new Brep();
            DA.GetData(0, ref subtractFromTemp);

            List<Brep> subtractWithTemp = new List<Brep>();
            DA.GetDataList(1, subtractWithTemp);

            double tol = DocumentTolerance();
            DA.GetData(2, ref tol);

            bool strict = new bool();
            DA.GetData(3, ref strict);

            bool accurate = new bool();
            DA.GetData(4, ref accurate);

            BrepBB subtractFrom = new BrepBB(subtractFromTemp, accurate);

            List<BrepBB> subtractWith = new List<BrepBB>();
            {
                foreach (Brep b in subtractWithTemp)
                {
                    subtractWith.Add(new BrepBB(b, accurate));  //this code runs more than necessary
                }
            }

            //Queue up the task
            Task<SolveResults> task = Task.Run(() => ComputeDifference(subtractFrom, subtractWith, tol, strict, accurate), CancelToken);
            TaskList.Add(task);
            return;
        }

        if (!GetSolveResults(DA, out SolveResults result))
        {
            //Compute right here; collect input data
            Brep subtractFromTemp = new Brep();
            DA.GetData(0, ref subtractFromTemp);

            List<Brep> subtractWithTemp = new List<Brep>();
            DA.GetDataList(1, subtractWithTemp);
            
            double tol = DocumentTolerance();
            DA.GetData(2, ref tol);

            bool strict = new bool();
            DA.GetData(3, ref strict);

            bool accurate = new bool();
            DA.GetData(4, ref accurate);

            BrepBB subtractFrom = new BrepBB(subtractFromTemp, accurate);

            List<BrepBB> subtractWith = new List<BrepBB>();
            {
                foreach (Brep b in subtractWithTemp)
                {
                    subtractWith.Add(new BrepBB(b, accurate)); //this code runs more than necessary
                }
            }

            //Compute results on given data
            result = ComputeDifference(subtractFrom, subtractWith, tol, strict, accurate);
        }

        //Set output data
        if (result != null)
        {
            List<Brep> brepResults = new List<Brep>();
            foreach(BrepBB brepResult in result.Subtracted)
            {
                brepResults.Add(brepResult.Brep);
            }
            DA.SetDataList("Result", brepResults);
        }
    }

To make a long story short:

given a Brep List A and a Brep List B you want to perform bool ops (any) to items in A against items in B?

Hmmm… not exactly. I’m not sure how best to explain this.

I want to operate on a single Brep A with a list of Breps B within the code, which is what the code is doing. I just want to convert the list of Breps B into my class BrepBB before any list mapping is done - for instance when multiple breps are plugged into the Brep A input that only has item access causing the list of Brep B to be accessed multiple times which then causes the conversion to BrepBB to happen every time the list Brep B is accessed instead of just a single conversion. Otherwise the code doesn’t need list access on Brep A.

I could give Brep A list access and handle all of that list mapping myself, but then I run into a problem with multi-threading. If I do that, then I am not sure how to run more than one task in a single call of the solve instance. I’m sure there is some way to do that, but I’m not sure if that is possible with the native GH_TaskCapableComponent implementation, or at least I haven’t figured it out.

Reporting back on this - I was finally able to work around this somewhat. Instead of calculating redundant bounding boxes, I am caching them to a user dictionary and then looking up the results for subsequent redundant bounding box checks. To do this I needed to bring in the GH_Brep object from DA.GetData instead of bringing in the Brep like I typically would as bringing in the Brep was making unique copies on each run of DA.GetData causing the Dictionary lookup to fail. Getting the Brep with GH_Brep.Value would ensure that I am always referencing the right Brep.

If anyone else is doing this, it is import to make a copy of that Brep if you are going to edit it directly, as it can also edit upstream values causing some unexpected behavior.